/*
 * first-pxe.S86  -  MGL loader for PXE bootroms
 *
 * Copyright (C) 2003-2007 Gero Kuhlmann   <gero@gkminix.han.de>
 *
 *  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.
 *
 * $Id: first-pxe.S86,v 1.9 2007/01/06 18:31:38 gkminix Exp $
 *
 *====================================================================
 */
#define DEF_HEAP_SIZE
#define DEF_STACK_SIZE

#include <common.i86>
#include <netboot.i86>
#include <system/bios.i86>
#include <system/nbloader.i86>
#include <pxe/entry.i86>
#include "headers/osload.i86"
#include "headers/runtime.i86"
#include "first.i86"

.file __FILE__
.line __LINE__



/*
 *====================================================================
 *
 * Definition of heap and stack size
 */

/* Stack size is common for all netboot image loader programs */
__stack_size = MIN_STK_SIZE


/* We dont need a heap */
__heap_size = 0




.line __LINE__
/*
 *====================================================================
 *
 * Define some external routines from the i386 library
 */

	.text

	.extern	prnstr			# routines in scnio.S86
	.extern	prcrlf
	.extern	prndwd
	.extern	prnwrd
	.extern	prnbyt
	.extern	cvtnum

	.extern	chkcpu			# routines in cpu.S86

	.extern	movmem			# routines in misc.S86



/*
 *====================================================================
 *
 * Boot image loader header
 */

	jmp	start			# PXE entry point

	.org	NBLD_OFFSET

nbld_header:
	.word	__text_end		# total size of text segment
	.word	__bss_end		# total size of data segment
	.word	__heap_size		# minimum heap size
	.word	__stack_size		# minimum stack size
	.word	0			# boot image entry routine
	.word	0			# the main program
	.word	vmagic			# vendor ID
	.word	sigmsg			# copyright message
	.word	0			# no user information



/*
 *====================================================================
 *
 * Now start the game. For a PXE boot image we have to setup everything
 * (like BSS segment etc.) ourselves.
 * This program gets called by the PXE bootrom with a far pointer to
 * the !PXE structure on the stack, and a far pointer to the PXENV+
 * structure in ES:BX.
 */

start:

	push	es			# save PXENV+ pointer
	push	bx

/*
 * The assembler assumes the text segment to start at a 0 offset, while
 * the PXE bootrom calls us at 0x7C00, so we have to first adjust the
 * CS register.
 */

	call	1f
1:	pop	bx			# compute offset difference
	sub	bx,offset 1b
	mov	cx,bx
	shr	cx,1			# use single bit shift here because
	shr	cx,1			# we dont know yet what processor we
	shr	cx,1			# are running on
	shr	cx,1
	mov	ax,cs
	add	ax,cx
	mov	cx,offset 2f
	push	ax			# push new segment and offset and load
	push	cx			# them by using a far return
	lret

/*
 * Set the DS and ES registers.
 */

2:	mov	bx,offset __text_end
	add	bx,0x000F
	shr	bx,1
	shr	bx,1			# compute real start of data segment
	shr	bx,1
	shr	bx,1
	add	ax,bx
	push	ds			# save DS
	mov	ds,ax			# set new DS and ES
	mov	es,ax

/*
 * Print the copyright message and check that we are running on the
 * correct CPU. These routine dont use the BSS segment and can therefore
 * be run before setting up the BSS.
 */

	mov	si,cs:[nbld_copyright]
	call	prnstr			# print copyright message
	call	chkcpu
	or	si,si			# check for correct CPU
	jz	3f
8:	call	prnstr			# print error message
	call	prcrlf
	pop	ds
	pop	bx
	pop	es			# return back to the bootrom
	lret

/*
 * The mknbi program puts the compiled MGL program right at the end of
 * this boot image loader binary, and therefore right into the BSS
 * segment. In order to be able to use the BSS segment, we first have
 * to move the MGL program out of the way.
 * Note that we cant use the getmem routine here, because it requires
 * that BSS has been initialized, but we can do that only after the MGL
 * program has been copied. The movmem routine does not use the BSS
 * segment.
 */

3:	xor	eax,eax
	mov	ax,cs
	shl	eax,4
	add	eax,file_size_value	# compute beginning of MGL program

	xor	esi,esi
	mov	si,ds			# determine offset to beginning of
	shl	esi,4			# MGL runtime within DS
	sub	esi,eax
	jnc	4f
	neg	esi
	cmp	esi,0x00010000 - 1024	# should not exceed segment boundary
	ja	4f

	mov	edx,rtdata_loadsize[si + RT_DATAOFS]
	add	edx,0x00000003
	and	edx,0xFFFFFFFC		# load MGL size in dwords
	mov	ecx,edx

	add	edx,eax			# check if end of MGL program exceeds
	jc	4f			# conventional memory
	add	edx,0x000003FF
	jc	4f
	shr	edx,10			# round end of MGL program to kB
	push	eax
	int	INT_MEMORY		# get amount of conventional memory
	movzx32	eax,ax
	cmp	eax,edx			# compare end of MGL program to end
	pop	eax			# of conventional memory
	jae	5f
4:	mov	si,offset sizerr
	jmp	8b

5:	xor	ebx,ebx
	mov	bx,ds
	shl	ebx,4			# compute end of BSS segment
	add	ebx,offset __bss_end
	add	ebx,stack_size_value
	add	ebx,heap_size_value + 15
	and	ebx,0xFFFFFFF0		# round to next segment
	mov	edx,ecx
	add	edx,ebx			# check if new end of MGL program
	jc	4b			# exceeds conventional memory
	add	edx,0x000003FF
	jc	4b
	shr	edx,10			# round new end of MGL program to kB
	push	eax
	int	INT_MEMORY		# get amount of conventional memory
	movzx32	eax,ax
	cmp	eax,edx			# compare new end of MGL program to
	pop	eax			# end of conventional memory
	jb	4b

	call	movmem			# now copy the MGL program to its
	jnc	6f			# new place
	mov	si,offset cpyerr
	jmp	8b

6:	mov	di,offset __bss_start
	mov	cx,offset __bss_end
	sub	cx,di
	inc	cx
	shr	cx,1
	xor	ax,ax			# clear BSS segment
	rep	stosw

	shl	ebx,12			# convert new beginning of MGL program
	test	bx,bx			# into SEG:OFS
	jnz	4b
	mov	[startadr],ebx		# save MGL start address

	pop	word ptr [oldDS]	# save old DS
	pop	dword ptr [pxenvptr]	# save PXENV+ pointer

	push	bp
	mov	bp,sp
	mov	eax,[bp + 6]		# save !PXE pointer
	mov	[pxeptr],eax
	pop	bp

/*
 * Setup new stack
 */

	mov	[oldSS],ss		# save bootrom stack pointer
	mov	[oldSP],sp
	mov	ax,offset __bss_end
	add	ax,stack_size_value	# compute new stack pointer
	add	ax,heap_size_value
	mov	[stktop],ax
	mov	bx,ds

	cli
	mov	ss,bx			# switch to new stack
	mov	sp,ax
	sti

/*
 * Prepare the interface structure. We have to let the runtime module
 * determine the address of the BOOTP buffer by itself.
 * If the interrupt vector number 3 has been changed so that it points
 * into the base memory (i.e. below 0xA0000), it is an indication that
 * a debugger is active, so we set the TRAP loader flag.
 */

	mov	word ptr rtload_flags[rtload],RTFLAG_RUN
	mov	word ptr rtload_bsize[rtload],0
	mov	dword ptr rtload_bootp[rtload],0

	push	es
	les	di,[pxeptr]
	mov	eax,es:o_ps_sig[di]
	pop	es
	mov	edx,[pxeptr]		# check if we have a !PXE structure
	cmp	eax,[pxesig]
	je	1f
	mov	edx,[pxenvptr]		# if not, save the PXENV+ pointer
1:	mov	dword ptr rtload_pxe[rtload],edx

	push	es
	xor	ax,ax
	mov	es,ax
	mov	eax,es:[NB_SERVICE_INT * 4]
	or	eax,eax
	jz	2f
	mov	ah,NB_SERVICE_CHECK	# check if we have a netboot bootrom
	int	NB_SERVICE_INT		# this call is not supported by an
	cmp	ax,NB_SERVICE_MAGIC	# etherboot bootrom
	jne	2f
	or	word ptr rtload_flags[rtload],RTFLAG_LOAD
	inc	byte ptr [nbrom]
2:	cmp	word ptr es:[3 * 4 + 2],0xA000
	jae	0f
	or	word ptr rtload_flags[rtload],RTFLAG_TRAP
0:	pop	es

/*
 * Now call the MGL program
 */

	mov	si,offset rtload
	lcall	[startadr]		# call the MGL program
	cmp	ax,RTERROR_RUN
	je	4f

	xor	bx,bx
	test	byte ptr [nbrom],0xFF
	jz	3f
	cmp	ax,RTERROR_LOAD		# if we have a netboot bootrom we
	jne	3f			# can return with a reload return code
	inc	bx
3:	mov	ax,bx
	cli
	mov	ss,[oldSS]		# switch back to old stack
	mov	sp,[oldSP]
	sti
	les	bx,[pxenvptr]		# restore bootrom registers
	mov	ds,[oldDS]
	lret				# return to the PXE bootrom

/*
 * The MGL program indicated that we have to copy the loaded image into
 * place. For this we install a short copy program.
 */

4:	cld
	push	es
	xor	ax,ax
	mov	es,ax
	mov	di,OSLOAD_ADDR		# copy boot image loader into low
	mov	si,offset loadstart	# memory
	mov	cx,offset loadend
	sub	cx,si
	rep	movsb
	pop	es

/*
 * We have to switch to a suitable stack before calling the loaded program.
 * If a netboot bootrom is installed we can simply use the bootrom stack.
 * The bootrom provided enough space on the stack when this program got
 * called, and it is possible to return to the netboot bootrom even when
 * the IP stack has been terminated.
 * Note that the bootrom return address is already on the stack.
 */

	xor	ax,ax
	mov	fs,ax
	mov	bx,[oldDS]
	mov	fs:image_reg_ds[IMAGE_REC_ADDR],bx
	test	byte ptr [nbrom],0xFF	# check if we have a netboot
	jz	8f			# bootrom

5:	cli
	mov	ss,[oldSS]		# switch back to old stack
	mov	sp,[oldSP]
	sti

	push	bp
	mov	bp,sp
	mov	eax,fs:image_stack[IMAGE_REC_ADDR + 0]
	or	eax,eax
	jz	6f			# store pointer to image header
	mov	[bp + 2],eax		# or !PXE structure
6:	mov	eax,fs:image_stack[IMAGE_REC_ADDR + 4]
	or	eax,eax
	jz	7f			# store pointer to BOOTP record
	mov	[bp + 6],eax		# if necessary
7:	pop	bp
	ljmp	0,OSLOAD_ADDR		# call OS loader

/*
 * When we got called by a PXE bootrom, there is a problem: the bootrom
 * stack just provides space for one far pointer (which is the !PXE structure
 * pointer upon entry). For a netboot boot image we need space for two far
 * pointers. It is not possible to adjust the stack accordingly, because
 * upon return to the bootrom only this single pointer gets cleaned off
 * the stack. Anything else is not backed by the PXE spec. For this case
 * we switch to a temporary stack, and the return pointer will reboot the
 * system.
 * If only one pointer needs to get copied onto the stack, we can use the
 * old PXE bootrom stack, if the bootrom services havent been terminated.
 */

8:	mov	eax,fs:image_stack[IMAGE_REC_ADDR + 4]
	or	eax,eax
	jnz	0f
	cmp	byte ptr fs:image_hflags1[IMAGE_REC_ADDR],FLAG1_KEEP_ALL
	je	5b

0:	cli
	xor	ax,ax
	mov	ss,ax			# switch to intermediate stack
	mov	sp,OSLOAD_STACK
	sti

	push	dword ptr fs:image_stack[IMAGE_REC_ADDR + 4]
	push	dword ptr fs:image_stack[IMAGE_REC_ADDR + 0]
	mov	eax,fs:[INT_BOOTFAIL * 4]
	cmp	byte ptr fs:image_hflags1[IMAGE_REC_ADDR],0
	je	9f
	mov	eax,(BIOS_RESET_SEG * 65536) + BIOS_RESET_OFS
9:	push	eax
	xor	eax,eax
	ljmp	0,OSLOAD_ADDR		# call OS loader




/*
 *====================================================================
 *
 * String and constants definitions
 */
	_rodata


	.extern	loadstart
	.extern	loadend


/* Startup signature */
sigmsg:	.byte	0x0D, 0x0A
	.ascii	"MGL PXE Boot Image Loader "
	.ascii	VERSION
	.byte	0x0D, 0x0A
	.ascii	COPYRIGHT
	.byte	0x0D, 0x0A
	.byte	0x0D, 0x0A
	.byte	0


/* Boot image header vendor magic ID */
vmagic:	.asciz	VENDOR_MAGIC


/* Signature of !PXE structure */
pxesig:	.ascii	"!PXE"


/* Error messages */
sizerr:	.asciz	"Invalid boot loader size"
cpyerr:	.asciz	"MGL copy error"




/*
 *====================================================================
 *
 * Variable definitions
 */
	.bss

	.global	stktop

	.lcomm	oldSS,2			# bootrom stack segment
	.lcomm	oldSP,2			# bootrom stack pointer
	.lcomm	oldDS,2			# bootrom data segment
	.lcomm	startadr,4		# start address of MGL program
	.lcomm	stktop,2		# pointer to top of stack
	.lcomm	pxenvptr,4		# far pointer to PXENV+ struct
	.lcomm	pxeptr,4		# far pointer to !PXE struct
	.lcomm	rtload,RTLOAD_SIZE	# runtime loader interface structure
	.lcomm	nbrom,1			# flag if we have a netboot bootrom




/*
 *====================================================================
 */
	.end

