/*
 **************************************************************************
 *
 * Boot-ROM-Code to load an operating system across a TCP/IP network.
 *
 * Module:  kernel.S86
 * Purpose: Setup the stack and data areas, and then call the higher
 *          level routines.
 * Entries: Called by loader at offset 3, callpxe, dochecksum
 *
 **************************************************************************
 *
 * Copyright (C) 1995-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: kernel.S86,v 1.13 2007/02/01 12:09:20 gkminix Exp $
 *
 */


/*
 **************************************************************************
 *
 * Load assembler macros.
 *
 */
#define DEF_STACK_SIZE 1
#define DEF_HEAP_SIZE 1

#include <common.i86>
#include <romconfig.i86>
#include <system/bootrom.i86>
#include <system/bios.i86>
#include <pxe/general.i86>
#include <pxe/entry.i86>
#include <pxe/romid.i86>
#include <pxe/loader.i86>
#include <pxe/base.i86>
#include <pxe/undi.i86>

.file __FILE__
.line __LINE__



/*
 **************************************************************************
 *
 * Define the kernel stack and heap size
 *
 */

/* Stack size, as defined in romconfig.i86 */
__stack_size = KRNSTACK


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




.line __LINE__
/*
 **************************************************************************
 *
 * Define the PXE function tables
 *
 * Note that GCC puts these into the data segment, and not into the
 * rodata segment.
 *
 */
	.data

	.extern	_base_func_tab
	.extern	_tftp_func_tab
	.extern	_udp_func_tab




/*
 **************************************************************************
 *
 * The kernel starts here. At the beginning there is some
 * miscellaneous data.
 *
 */
	.text

	.global	stkptr
	.global	rmdata
	.global	pmdata
	.global	callpxe
	.global	dochecksum

	.extern	prnchr
	.extern	prnstr
	.extern	prnbyt
	.extern	_do_boot


	jmp	pxentry			# jump to PXE entry point
start:	lret				# old loader entry point (unused)


/*
 * These values can be used by the binary patch program to strip off the
 * BSS segment from the final binary. The BSS segment just contains zeroes
 * which dont have to be saved in the tight ROM. The totsiz variable will
 * be filled in by the binary patch program.
 */

	.org	DATAOFS

	.word	__text_end
	.word	__data_end
	.word	romid			# offset to ROM ID structure
totsiz:	.word	0			# total number of paragaphs required


/*
 * Put the version number into the final kernel image. This allows the
 * binary patch program to verify that its using the correct kernel
 * image.
 */

	.byte	VER_MAJOR		# major version number
	.byte	VER_MINOR		# minor version number


/*
 * Kernel flags which modify the behaviour of the bootrom kernel. These
 * flags were not defined in netboot versions 0.10.1 and earlier. How-
 * ever, the initial flag value is always 0, while the first word of the
 * following ROM ID structure is always different from 0. When the binary
 * patch program finds the flag value to be different from 0, it must
 * assume that there is no flag word available.
 */

flags:	.word	0			# kernel flags


/*
 * Kernel ROM ID structure as defined in the PXE specification. Note that
 * this will not be placed into the ROM loader, as its not required there.
 * We also dont need to define a loader offset here.
 */

romid:	.ascii	KERNEL_ROMID_SIG	# structure ID
	.byte	KERNEL_ROMID_SIZE	# length of this structure
	.byte	0			# checksum (filled in by makerom)
	.byte	0			# revision of this structure
	.byte	0x00,0x01,0x02		# API revision number
	.word	krnstart		# offset to kernel loader
stksiz:	.word	__stack_size		# stack size required for kernel
	.word	__bss_end		# required data segment size
	.word	__text_end		# required code segment size


/*
 * The following variables have to be accessible using the CS register:
 */

drvseg:	.word	0			# segment of UNDI driver
rmdata:	.word	0			# our own real mode data segment
pmdata:	.word	0			# protected mode data segment selector
stkptr:	.word	0			# stack pointer when calling image
retadr:	.long	0			# ROM return address
oldstk:	.long	0			# old BIOS stack pointer



/*
 **************************************************************************
 *
 * Now start with the actual kernel code, which sets up the stack, copies
 * everything into its place, and then calls the higher level code.
 * Input:  1. Arg -  Far pointer to loader structure
 * Output: none
 * Registers changed: all
 *
 */
krnstart:

/*
 * First save all values passed to us by the bootrom loader, including the
 * return address. For the purposes of this kernel loader, we initially use
 * our code segment for DS.
 */

	push	cs
	pop	ds			# set temporary data segment
	pop	dword ptr [retadr]	# save return address
	mov	word ptr [oldstk + 0],sp
	mov	word ptr [oldstk + 2],ss
	mov	bp,sp			# save pointer to loader structure

/*
 * Determine the start segment of the whole bootrom area. The size of the
 * kernel stack segment is already included into the totsiz value. However,
 * if the UNDI driver requires more stack space, we have to increase the
 * stack accordingly. This code puts the end of available memory into DX.
 */

	int	INT_MEMORY
	shl	ax,6			# get the memory end segment
	mov	dx,ax
	mov	bx,ax
	les	di,[bp]			# get pointer to UNDI ROMID
	les	di,es:o_bcl_undi_romid[di]
	mov	ax,es			# check if pointer to UNDI ROMID
	or	ax,di			# structure is zero
	jnz	1f
	mov	eax,file_size_value
	mov	di,ax
	and	di,0x000F		# put pointer to UNDI start into
	shr	eax,4			# ES:SI
	mov	cx,cs
	add	ax,cx
	mov	es,ax
	mov	si,di
	sub	bx,es:[di + NETSIZEOFS]	# compute start of UNDI memory area
	add	di,es:[di + NETROMIDOFS]
	call	checkundi		# put pointer to UNDI ROMID into
	jc	fatal			# ES:DI
	jmp	2f

1:	call	checkundi
	jc	fatal
	xor	si,si			# put pointer to UNDI start into
	mov	ax,es:o_ur_datasize[di]	# ES:SI, and to the UNDI ROMID
	add	ax,0x000F		# structure into ES:DI
	rcr	ax,1			# convert data size into paragraphs
	shr	ax,3
	sub	bx,ax
	mov	ax,es:o_ur_codesize[di]
	add	ax,0x000F
	rcr	ax,1			# convert code size into paragraphs
	shr	ax,3
	sub	bx,ax

2:	mov	[drvseg],bx
	mov	ax,es:o_ur_stacksize[di]
	cmp	ax,[stksiz]
	jbe	3f			# compute stack size
	sub	ax,[stksiz]
	add	ax,[totsiz]		# add max. stack size to total kernel
	jnc	4f			# memory size
0:	mov	ax,PXENV_STATUS_LOADER_NO_FBM
	jmp	fatal

3:	mov	ax,[totsiz]
4:	cmp	ax,0x1000		# check that the total kernel memory
	jae	0b			# size is not larger than 64kB
	sub	bx,ax			# compute start of bootrom area
	jc	0b
	cmp	bx,OSENDMEM		# check that we have enough memory
	jb	0b
	mov	word ptr [rmdata],bx

/*
 * Clear the whole memory area used by the bootrom. This is easier
 * than clearing the BSS segments seperately. Remember not to change DX!
 */

	push	es
	push	di
	push	si
	xor	ax,ax
5:	mov	es,bx			# since the total area can be larger
	xor	di,di			# than 64kB we have to clear each
	mov	cx,8			# paragraph seperately.
	rep	stosw
	inc	bx
	cmp	bx,dx
	jb	5b

/*
 * Copy the whole kernel code into its final place. Then compute
 * the new address of data, BSS and stack segment. Do not change DX!
 * At the end, the start of the text segment is in FS.
 */

	mov	ax,[drvseg]
	mov	cx,offset __text_end + 0x000F
	and	cx,0xFFF0		# adjust size of text segment to
	mov	bx,cx			# paragraph alignment
	shr	bx,4
	sub	ax,bx			# compute start of text segment
	mov	es,ax
	mov	fs,ax
	xor	di,di
	xor	si,si
	shr	cx,1			# copy code segment
	rep	movsw

	mov	bx,[rmdata]
	mov	es,bx
	xor	di,di
	mov	cx,offset __data_end + 1
	shr	cx,1			# copy data segment
	rep	movsw

	sub	ax,bx
	shl	ax,4			# compute new stack pointer - put it
	and	ax,0xFFFE		# at the end of the data segment
	mov	[stkptr],ax

	push	ds
	lds	si,[bp]
	mov	cx,bc_loader_size / 2
	sub	ax,undi_loader_size	# copy base code loader structure into
	mov	di,ax			# new stack
	rep	movsw
	pop	ds
	pop	si
	pop	di			# restore pointer to UNDI ROMID struct
	pop	es

	cli
	mov	ss,bx
	mov	sp,ax			# set new stack pointer
	sti

/*
 * Prepare the UNDI loader structure onto the stack. The top of free
 * memory is still in DX.
 */

	mov	bp,sp
	mov	ax,[drvseg]		# save beginning of UNDI data segment
	mov	o_ul_undi_ds[bp],ax
	mov	ax,es:o_ur_codesize[di]
	add	ax,0x000F		# compute beginning of UNDI code
	rcr	ax,1			# segment
	shr	ax,3
	sub	dx,ax
	mov	o_ul_undi_cs[bp],dx

/*
 * Call the network driver loader to copy the network driver into memory.
 * The network driver will return with a far return, which will directly go
 * into the new copy of the kernel.
 */

	push	ss			# push pointer to loader structure
	push	bp
	push	fs
	push	offset 1f		# push return address
	push	es
	add	si,es:o_ur_loadofs[di]	# push pointer to UNDI loader routine
	push	si
	lret				# call the network driver loader

/*
 * We are now running in the new copy of the bootrom kernel. Save the pointers
 * to the PXENV and !PXE structures. Also save the kernel flags out of the code
 * segment.
 */

1:	pop	bp			# get pointer to UNDI loader structure
	pop	ds			# get new data segment
	mov	ax,o_ul_status[bp]
	mov	ebx,o_ul_pxeptr[bp]	# save pointer to !PXE structure
	mov	[pxeptr],ebx
	mov	ebx,o_ul_pxenvptr[bp]	# save pointer to PXENV+ structure
	mov	[pxenvptr],ebx
	mov	bx,cs:[flags]		# save kernel flags
	mov	[krnflags],bx
	or	ax,ax			# check for network driver loader error
	jnz	fatal

/*
 * Now set all required values in the PXENV and !PXE structures, which have
 * been setup by the network driver loader.
 */

	lds	si,[pxenvptr]
	mov	bx,ss
	mov	dx,cs
	sub	dx,bx			# compute size of stack segment
	shl	dx,4
	mov	cx,offset __text_end
	mov	o_pe_bc_ss_sel[si],bx
	mov	o_pe_bc_ss_size[si],dx
	mov	o_pe_bc_ds_sel[si],bx
	mov	o_pe_bc_ds_size[si],dx
	mov	o_pe_bc_cs_sel[si],cs
	mov	o_pe_bc_cs_size[si],cx
	movzx	cx,byte ptr o_pe_length[si]
	call	dochecksum
	sub	byte ptr o_pe_checksum[si],al

	lds	si,ss:[pxeptr]
	mov	word ptr o_ps_baseromid[si + 0],offset romid
	mov	word ptr o_ps_baseromid[si + 2],cs
	mov	o_ps_desc[si + (PXE_DESC_STACK * desc_size) + o_desc_segment],bx
	mov	o_ps_desc[si + (PXE_DESC_BASE_DATA * desc_size) + o_desc_segment],bx
	mov	o_ps_desc[si + (PXE_DESC_BASE_CODE * desc_size) + o_desc_segment],cs
	mov	o_ps_desc[si + (PXE_DESC_BASE_WRITE * desc_size) + o_desc_segment],cs

	movzx32	eax,bx			# compute linear base address of data
	shl	eax,4			# segment
	mov	o_ps_desc[si + (PXE_DESC_STACK * desc_size) + o_desc_addr],eax
	mov	o_ps_desc[si + (PXE_DESC_STACK * desc_size) + o_desc_size],dx
	mov	o_ps_desc[si + (PXE_DESC_BASE_DATA * desc_size) + o_desc_addr],eax
	mov	o_ps_desc[si + (PXE_DESC_BASE_DATA * desc_size) + o_desc_size],dx

	movzx32	eax,bx			# compute linear base address of text
	shl	eax,4			# segment
	mov	cx,offset __text_end
	mov	o_ps_desc[si + (PXE_DESC_BASE_CODE * desc_size) + o_desc_addr],eax
	mov	o_ps_desc[si + (PXE_DESC_BASE_CODE * desc_size) + o_desc_size],cx
	mov	o_ps_desc[si + (PXE_DESC_BASE_WRITE * desc_size) + o_desc_addr],eax
	mov	o_ps_desc[si + (PXE_DESC_BASE_WRITE * desc_size) + o_desc_size],cx

	mov	al,o_ps_descnum[si]
	cmp	al,PXE_DESC_DEF_BASE
	jae	5f
	mov	al,PXE_DESC_DEF_BASE
	mov	o_ps_descnum[si],al
5:	mov	ah,desc_size		# compute total size of !PXE
	mul	ah			# structure
	add	ax,pxe_struct_size
	mov	o_ps_length[si],al

	mov	cx,ax			# compute checksum of !PXE
	call	dochecksum		# structure
	sub	byte ptr o_ps_checksum[si],al

/*
 * Now call the kernel to start the netboot process. The PXE spec says to
 * call START_BASE, STOP_BASE and STOP_UNDI in this order. All this func-
 * tionality will be performed in the _do_boot routine.
 */

	mov	ax,ss			# set the data segment registers
	mov	ds,ax
	mov	es,ax
	movzx32	eax,bp			# get pointer to parameter structure
	call	_do_boot		# start the game

/*
 * Return to the BIOS if the main program returns, after printing an
 * error message.
 */

fatal:	sti				# make sure interrupts are enabled
	mov	bx,cs			# restore the data segment
	mov	ds,bx
	or	ax,ax			# check if we have to print the error
	jz	4f			# number

	push	ax
	mov	si,offset pxemsg	# print first error message
	call	prnstr
	pop	ax
	call	prnbyt

4:	mov	si,offset errmsg	# print second error message
	call	prnstr
	mov	eax,cs:[retadr]		# check if return address is valid
	or	eax,eax
3:	jz	3b			# if invalid, loop forever
	lss	sp,cs:[oldstk]		# restore old stack pointer
	ljmp	cs:[retadr]		# return to caller


/*
 * Messages and strings:
 */

pxemsg:	.byte	0x0D,0x0A
	.asciz	"Bootrom error: PXE-"

errmsg:	.byte	0x0D,0x0A
	.ascii	"Network boot failed"
	.byte	0x0D,0x0A
	.byte	0

drvsig:	.ascii	UNDI_ROMID_SIG



/*
 **************************************************************************
 *
 * Compute checksum of a given memory area
 * Input:  DS:SI  -  pointer to memory area
 *         CX     -  size of memory area
 * Output: AL     -  checksum
 * Registers changed: AX,CX
 *
 */
dochecksum:

	cld
	push	si
	xor	ax,ax
1:	lodsb				# add all bytes together
	add	ah,al
	loop	1b
	mov	al,ah
	xor	ah,ah
	pop	si
	ret



/*
 **************************************************************************
 *
 * Check for UNDI ROMID structure.
 * Input:  ES:DI  -  pointer to UNDI ROMID structure
 * Output: AX     -  error code
 *         Carry flag set if error
 * Registers changed: EAX,CX
 */
checkundi:

	push	ds
	push	si
	mov	ax,es
	mov	ds,ax
	mov	eax,o_ur_sig[di]
	cmp	eax,cs:[drvsig]
	mov	ax,PXENV_STATUS_LOADER_NO_UNDI
	jne	8f
	mov	si,di
	mov	cx,UNDI_ROMID_SIZE
	call	dochecksum
	or	ax,ax
	jz	9f
	mov	ax,PXENV_STATUS_LOADER_BAD_UNDI
8:	stc
9:	pop	si
	pop	ds
	ret



/*
 **************************************************************************
 *
 * Call PXE API. This needs to be a seperate function so that it can get
 * exported to the rest of the kernel code.
 * Input:  AX     -  PXE function number
 *         SS:BX  -  pointer to PXE parameter structure
 * Output: AX     -  error code
 * Registers changed: AX
 *
 */
callpxe:

	push	fs
	push	ss
	push	bx
	push	ax
	lfs	bx,ss:[pxeptr]
	lcall	fs:o_ps_pxentry16[bx]
	pop	ax
	pop	bx
	pop	ax
	mov	ax,word ptr ss:[bx]
	pop	fs
	ret



/*
 **************************************************************************
 *
 * PXE API entry point for kernel services (aka base code). Since we
 * have to change the stack this is not reentrant. The main PXE API
 * entry point in the network driver already saved all registers so
 * we dont have to do it ourselves.
 * Input:  1. Arg -  function code
 *         2. Arg -  far pointer to parameter structure
 *         3. Arg -  far pointer to !PXE structure
 * Output: AX     -  error code
 * Registers changed: All except DS, SS, SP and BP
 *
 */
pxentry:

/*
 * Get all parameters off the stack. Then switch to our own stack
 * and data segment. Since the higher level routines assume SS = DS,
 * and because this routine can be called from 16-bit protected mode,
 * we use the stack segment value from the !PXE structure for both
 * segment registers.
 */

	push	bp
	mov	bp,sp
	push	ds
	mov	bx,[bp + 6]		# get function code
	les	di,[bp + 12]		# get pointer to !PXE structure

	mov	ax,PXENV_STATUS_FAILURE
	mov	edx,es:o_ps_desc[di + (PXE_DESC_STACK * desc_size) + o_desc_addr]
	cmp	edx,es:o_ps_desc[di + (PXE_DESC_BASE_DATA * desc_size) + o_desc_addr]
	jne	4f			# check if data segment = stack segment

	mov	cx,es:o_ps_desc[di + (PXE_DESC_BASE_DATA * desc_size) + o_desc_segment]
	mov	ds,cx			# set our own data segment

	mov	eax,cr0
	test	ax,0x0001		# check protected mode flag
	jz	5f
	pushfd
	pop	eax
	bt	eax,17			# check virtual-8086 mode flag
	jc	5f

	mov	fs,es:o_ps_desc[di + (PXE_DESC_BASE_WRITE * desc_size) + o_desc_segment]
	mov	fs:[pmdata],cx		# save protected mode data segment

	mov	ax,PXENV_STATUS_PMODE
	cmp	bx,BASE_GET_BINL_INFO	# we allow only UDP functions and
	je	6f			# GetCachedInfo in protected mode
	cmp	bx,_udp_func_tab[0]
	jb	4f
	cmp	bx,_udp_func_tab[2]
	jbe	6f
4:	les	di,[bp + 8]		# get pointer to parameter structure to
	jmp	8f			# save error code

5:	mov	ax,PXENV_STATUS_FAILURE
	cmp	cx,cs:[rmdata]		# check real mode data segment
	jne	4b

6:	mov	dx,es:o_ps_desc[di + (PXE_DESC_STACK * desc_size) + o_desc_segment]
	mov	ecx,es:o_ps_statuscallout[di]
	mov	[statcallout],ecx	# save status callout routine pointer

	mov	ax,PXENV_STATUS_FAILURE
	les	di,[bp + 8]		# get pointer to parameter structure
	mov	ecx,[bp + 12]		# get pointer to !PXE structure
	cmp	cx,[pxeptr + 0]
	jne	8f
	shr	ecx,16			# get segment of !PXE structure
	mov	[pxeptr + 2],cx		# save segment into !PXE pointer
	mov	[pxenvptr + 2],cx	# and PXENV+ pointer

	cli
	mov	ax,sp			# switch to our own stack
	mov	cx,ss
	mov	ss,dx
	mov	sp,word ptr cs:[stkptr]
	sti

	push	es			# save pointer to parameter structure
	push	di
	push	cx			# save the old stack pointer
	push	ax
	mov	bp,sp			# let BP point to the old stack pointer

/*
 * Check for general base code functions
 */

	mov	ax,PXENV_STATUS_BAD_FUNC
	mov	si,offset _base_func_tab
	cmp	bx,word ptr [si + 0]
	jb	1f
	cmp	bx,word ptr [si + 2]
	jbe	3f

/*
 * Check for TFTP functions
 */

1:	mov	si,offset _tftp_func_tab
	cmp	bx,word ptr [si + 0]
	jb	2f
	cmp	bx,word ptr [si + 2]
	jbe	3f

/*
 * Check for UDP functions
 */

2:	mov	si,offset _udp_func_tab
	cmp	bx,word ptr [si + 0]
	jb	7f
	cmp	bx,word ptr [si + 2]
	ja	7f

/*
 * Call the function handler with the parameter structure copied to the
 * local stack. This is necessary so that the higher level C code is able
 * to access it.
 * Note that the error code is still in AX, and the data segment is still
 * in DX.
 */

3:	sub	bx,[si + 0]		# subtract minimum value from index
	add	si,bx
	add	si,bx
	shl	bx,2			# convert index into offset
	lea	bx,[bx + si + 4]	# compute pointer into function table
	mov	cx,[bx + 0]		# check that we have a handler
	or	cx,cx
	jz	7f

	mov	cx,[bx + 4]		# determine number of bytes to copy
	sub	sp,cx			# make space for structure on stack
	movzx32	eax,sp			# determine pointer to parameter struct
	push	cx
	push	ax
	lds	si,[bp + 4]
	mov	es,dx
	mov	di,ax
	cld				# copy parameter structure onto local
	rep	movsb			# stack
	mov	ds,dx
	call	word ptr [bx + 0]	# call handler routine
	pop	si			# restore all parameters for copy
	pop	cx
	cld
	les	di,[bp + 4]		# now copy the parameter structure back
	rep	movsb			# remember not to change AX, which
	mov	di,[bp + 4]		# contains the error code

/*
 * Restore the old stack, set the return error code and restore
 * all previously stored registers.
 */

7:	lss	sp,[bp + 0]		# restore old stack pointer
8:	mov	es:[di],ax		# save return status code into
	or	ax,ax			# PXE parameter structure
	jz	9f
	mov	ax,PXENV_EXIT_FAILURE
	stc
9:	pop	ds
	pop	bp
	lret




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

	.global	pxenvptr
	.global	pxeptr
	.global	statcallout
	.global	krnflags


	.lcomm	pxenvptr,4		# far pointer to PXENV structure
	.lcomm	pxeptr,4		# far pointer to !PXE structure
	.lcomm	statcallout,4		# far pointer to callout routine
	.lcomm	krnflags,2		# kernel flags




/*
 **************************************************************************
 *
 */

	.end

