/*
 * nbentry.S86  -  routines to enter and leave a netboot image
 *
 * Copyright (C) 2002-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: nbentry.S86,v 1.10 2007/01/08 22:02:20 gkminix Exp $
 *
 *====================================================================
 */


#include <common.i86>
#include <netboot.i86>
#include <system/nbloader.i86>
#include <system/bios.i86>

.file __FILE__
.line __LINE__



/*
 *====================================================================
 *
 * Some definitions which are local to this module
 */

#define BOOT_FAIL_WAIT	8		/* Seconds to wait before rebooting */




.line __LINE__
/*
 *====================================================================
 *
 * Start the text segment and define all public entry points
 */
	.text

	.extern	ldrini			# define external symbols
	.extern	btpini
	.extern	prnstr
	.extern	prcrlf
	.extern	set_timeout
	.extern	chk_timeout

	.global	nbentry			# define global symbols
	.global	nbexit
	.global	doerr
	.global	nopxe



/*
 *====================================================================
 *
 * Startup a netboot image loader program. This routine gets directly
 * called by the bootrom, and then calls the image loader program after
 * initialization.
 * Input:  CS:0  - Pointer to netboot image definition structure
 * Output: none
 * Changed registers: all
 */
nbentry:

/*
 * First save some important registers. Especially, save all registers
 * which are required to return to the bootrom lateron.
 */

	cld
	push	di
	push	si
	push	bp
	push	ds
	push	es

/*
 * This routine gets called by the bootrom using a linear address,
 * and the actual start of the code segment does not necessarily
 * be equal to the contents of CS, so we correct this here.
 */

	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

/*
 * Now get the bootrom return address from the stack, save all important
 * bootrom registers into local variables, and switch to new stack.
 */

2:	mov	bx,offset __data_start
	or	bx,bx			# if the data segment starts at offset
	jnz	7f			# zero, we have seperate text and data
	mov	bx,offset __text_end	# segments
	add	bx,0x000F
	shr	bx,1
	shr	bx,1			# compute real start of data segment
	shr	bx,1
	shr	bx,1
	add	ax,bx
7:	mov	ds,ax
	mov	es,ax
	mov	di,offset __bss_start
	mov	cx,offset __bss_end
	sub	cx,di			# clear BSS segment
	xor	al,al
	rep	stosb
	pop	word ptr [oldES]
	pop	word ptr [oldDS]	# save old register values in case
	pop	word ptr [oldBP]	# we have to return to the boot rom
	pop	word ptr [oldSI]
	pop	word ptr [oldDI]

	mov	bp,sp
	pop	word ptr [romret + 0]	# get return address to bootrom
	pop	word ptr [romret + 2]
	mov	[oldSP],sp		# save boot rom stack pointer
	mov	[oldSS],ss

	mov	si,cs:[nbld_copyright]
	call	prnstr			# print copyright message

/*
 * Check that we are running on the correct processor. After we have passed
 * this check, we can be sure to use 32-bit opcodes and registers.
 */

	push	bp
	call	chkcpu
	pop	bp
	or	si,si			# dont use long conditional jump
	jz	4f			# because that doesnt work on a
	jmp	doerr1			# wrong CPU

/*
 * Check if we have been called by a PXE bootrom.  Calling this routine
 * using a PXE bootrom is invalid.
 */

4:	test	byte ptr cs:[pxeflg],0xFF
	jz	6f
	mov	si,offset pxeerr	# PXE not supported
	jmp	doerr1

/*
 * Check that the boot image header has a correct magic number and vendor
 * magic ID string. Also determine the top of available memory for stack
 * and heap purposes. Note that FS:DI has to point to the boot image header.
 */

6:	lfs	di,[bp + 4]
	mov	dx,cs:[nbld_vendid]
	call	ldrini			# initialize load record handling
	or	si,si
	jnz	doerr1

	mov	eax,fs:boot_ld_mlength[di]
	dec	eax
	and	eax,0xFFFFFFFE		# get size of memory block allo-
	test	eax,0xFFFF0000		# cated for this boot loader
	jz	7f
	mov	eax,0x0000FFFE		# limit to 64 kB
7:	mov	bx,ds
	mov	dx,cs			# determine offset from beginning
	sub	bx,dx			# of load record to beginning of
	shl	bx,4			# data segment
	sub	ax,bx
	mov	si,offset memerr
	mov	bx,offset __bss_end	# check if enough stack available
	add	bx,cs:[nbld_heapsize]
	add	bx,cs:[nbld_stacksize]
	cmp	ax,bx
	jb	doerr1
	mov	[stktop],ax		# save top of stack

/*
 * Check if BOOTP/DHCP record is valid, and copy it out of the bootrom
 * area into some local memory space. We have to do this before switching
 * to a new stack because we still need to access the old bootrom stack
 * to get the pointer to the BOOTP record.
 */

	mov	eax,[bp + 8]
	mov	[btpptr],eax
	lfs	si,[btpptr]
	call	btpini			# load BOOTP/DHCP record
	or	si,si
	jnz	doerr1

/*
 * Switch to a local stack to avoid overflowing the bootrom stack.
 */

	cli
	mov	bx,ds
	mov	ss,bx			# set new stack to end of free
	mov	sp,[stktop]		# memory segment
	sti
	jmp	word ptr cs:[nbld_nbstart]



/*
 *====================================================================
 *
 * Print error message and return to the bootrom.
 * Input:  SI    - Offset to error message
 * Output: Routine doesnt return
 * Changed registers: all
 */
doerr:	mov	ax,ss			# restore DS if changed
	mov	ds,ax
doerr1:	or	si,si
	jz	doerr2
	push	si
	mov	si,offset lderr
	call	prnstr
	pop	si
	call	prnstr			# print error message
	call	prcrlf
doerr2:	xor	ax,ax			# indicate to terminate the boot
					# fall through to nbexit


/*
 *====================================================================
 *
 * Return to the bootrom or to the BIOS as indicated by caller. If
 * we are not going to return to the bootrom, we have to wait for
 * some time (or a key press) because sometimes the BIOS clears the
 * the screen and the user will be unable to see any error message.
 * Input:  AX    -  Return code for the bootrom
 * Output: Routine doesnt return
 * Changed registers: all
 */
nbexit:

	push	ax
	mov	bx,[doexit]		# check if exit helper routine defined
	or	bx,bx
	jz	1f
	call	bx			# call exit helper routine

1:	xor	ax,ax
	mov	es,ax			# check if we are going to call the
	mov	eax,[romret]		# boot fail vector instead of bootrom
	cmp	eax,es:[INT_BOOTFAIL * 4]
	jne	3f
	mov	ax,BOOT_FAIL_WAIT
	call	set_timeout
2:	call	chk_timeout		# wait for some seconds before
	jnc	2b			# proceeding
	jmp	8f

3:	cmp	eax,(BIOS_RESET_SEG * 65536) + BIOS_RESET_OFS
	jne	8f
	call	prcrlf
	mov	si,offset keyerr	# print error message and wait for
	call	prnstr			# key press
	xor	ah,ah
	int	INT_KBD

8:	pop	ax
	cli
	mov	ss,[oldSS]
	mov	sp,[oldSP]		# set boot rom stack and return
	sti				# to boot rom with registers set
	push	dword ptr [romret]	# correctly
	mov	si,[oldSI]
	mov	di,[oldDI]
	mov	bp,[oldBP]
	mov	es,[oldES]
	mov	ds,[oldDS]
	lret				# return to bootrom



/*
 *====================================================================
 *
 * PXE entry point when PXE services not supported.
 * Input:  None
 * Output: None
 * Changed registers: all
 */
nopxe:

/*
 * A PXE bootrom calls us at 0000:7C00, but the assembler assumes the
 * start of the code segment at offset 0, so we compute the correct
 * offset.
 */

	call	1f
1:	pop	bx			# compute offset difference
	sub	bx,offset 1b
	inc	cs:byte ptr pxeflg[bx]	# increment PXE calling flag
	jmp	nbentry			# the rest will be done at nbentry


/*
 * Because the data area hasnt been initialized yet when calling nbentry, we
 * have to store the PXE calling flag within the text segment.
 */

pxeflg:	.byte	0




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


/*
 * Error messages
 */
lderr:	.asciz	"Boot image loader: "
memerr:	.asciz	"Internal memory error"
pxeerr:	.asciz	"PXE not supported"
keyerr:	.asciz	"Press any key to reboot"




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

	.global	romret
	.global	btpptr
	.global	doexit
	.global	stktop


	.lcomm	oldDS,2			# old DS from boot rom
	.lcomm	oldES,2			# old ES from boot rom
	.lcomm	oldBP,2			# old BP from boot rom
	.lcomm	oldSI,2			# old SI from boot rom
	.lcomm	oldDI,2			# old DI from boot rom
	.lcomm	oldSP,2			# old SP from boot rom
	.lcomm	oldSS,2			# old SS from boot rom

	.lcomm	romret,4		# return address to bootrom
	.lcomm	btpptr,4		# pointer to bootrom BOOTP record
	.lcomm	doexit,2		# pointer to exit helper routine
	.lcomm	stktop,2		# pointer to top of stack




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

