/*
 * first.S86  -  primary boot loader
 *
 * Copyright (C) 1995-2007 Gero Kuhlmann   <gero@gkminix.han.de>
 * Copyright (C) 1996,1997 Gero Kuhlmann   <gero@gkminix.han.de>
 *                and Markus Gutschke <gutschk@math.uni-muenster.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.S86,v 1.10 2007/01/06 18:31:26 gkminix Exp $
 *
 *====================================================================
 */
#define DEF_STACK_SIZE 1
#define DEF_HEAP_SIZE 1

#include <common.i86>
#include <netboot.i86>
#include <net/bootp.i86>
#include <system/nbloader.i86>
#include <system/bios.i86>
#include "first.i86"

.file __FILE__
.line __LINE__



/*
 * Care for debugging symbols
 */
.ifndef ASM_DEBUG_VERBOSE
	ASM_DEBUG_VERBOSE = 0
.endif
.ifndef ASM_DEBUG
.ifgt ASM_DEBUG_VERBOSE
	ASM_DEBUG = 1
.else
	ASM_DEBUG = 0
.endif
.endif
.ifndef ASM_FREEZE_AFTER_INIT
	ASM_FREEZE_AFTER_INIT = 0
.endif
.ifndef ASM_KEYPRESS_AFTER_INIT
	ASM_KEYPRESS_AFTER_INIT = 0
.endif



/*
 *====================================================================
 *
 * Define stack and heap 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	nbentry			# routines in nbentry.s86
	.extern	doerr

	.extern	ldrlen			# routines in ldrec.s86
	.extern	ldradr
	.extern	ldrfnd

	.extern	btptag			# routines in bootp.s86

	.extern	strlen			# routines in string.s86
	.extern	strstr

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

	.extern	getext			# routines in misc.s86
	.extern	getmem
	.extern	movmem



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

	jmp	nopxe			# 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	nbentry			# boot image entry routine
	.word	start			# the main program
	.word	vmagic			# vendor ID
	.word	sigmsg			# copyright message
	.word	0			# no user information



/*
 *====================================================================
 *
 * Now start the game
 */

start:

/*
 * Get the size of the base memory from the BIOS. We need at least
 * 616 kB.
 */

	call	getmem			# get memory size in kB

.ifgt ASM_DEBUG
	mov	dx,ax
	mov	si,offset memmsg
	call	prnstr
	mov	ax,dx			# let the user know about the amount
	call	prnwrd			# of memory available
	call	prcrlf
	mov	ax,dx
.endif

	mov	si,offset memerr	# check if enough
	cmp	ax,MIN_MEM
	jb	doerr

/*
 * Get the size of extended memory from the BIOS. This is needed for computing
 * the address of a loaded ramdisk image.
 */

	call	getext

.ifgt ASM_DEBUG
	mov	edx,eax
	mov	si,offset extmsg
	call	prnstr
	mov	eax,edx			# let the user know about the amount
	call	prndwd			# of memory available
	call	prcrlf
	mov	eax,edx
.endif

	mov	si,offset memerr
	cmp	eax,MIN_EXT		# we need at least 4MB
	jb	doerr

/*
 * Now we can check that all load segments for the lower memory including the
 * stack are within a range of 40kB, which is required by the Linux kernel
 * setup code.
 */

	lfs	di,[header]
	xor	edx,edx			# EDX contains the highest block found
	xor	ecx,ecx			# ECX contains the lowest block found
	dec	ecx
1:	call	ldrlen			# get the pointer to first load record
	add	di,ax
	call	ldradr			# get the load address
	jc	3f
	test	eax,0xFFF00000		# only allow addresses in lower 1MB
	jnz	3f
	cmp	eax,ecx			# check for lowest memory address
	jae	2f
	mov	ecx,eax
2:	add	eax,fs:boot_ld_mlength[di]
	cmp	eax,edx			# check for highest memory address
	jbe	3f
	mov	edx,eax
3:	test	byte ptr fs:boot_ld_flags[di],BOOT_FLAG_EOF
	jz	1b			# check for last record

	mov	si,offset recerr
	sub	edx,ecx			# compute used memory size
	jbe	doerr
	mov	eax,edx
	cmp	eax,MAX_STATIC_SIZE	# should not be too large
	ja	doerr
	mov	[lowsiz],dx		# save size for later

	test	ecx,0xFFF0000F		# lowest address has to be on a
	jnz	doerr			# segment boundary
	shr	ecx,4			# convert to segment
	mov	[lowmem],cx		# and save for later

.ifgt ASM_DEBUG
	mov	si,offset lowmsg
	call	prnstr
	mov	ax,cx			# let the user know about the
	call	prnwrd			# segment and size of used memory
	mov	al,0x28
	call	prnchr
	mov	ax,dx
	call	prnwrd
	mov	al,0x29
	call	prnchr
	call	prcrlf
.endif

/*
 * Next we can check that the Linux floppy boot sector is the first
 * segment in lower memory. This is required by the kernel.
 */

	mov	si,offset recerr
	mov	al,INITNUM + LOADER_TAG
	call	ldrfnd			# find load record for INIT seg
	jc	doerr			# isnt there?
	call	ldradr			# get address
	jc	doerr
	test	eax,0xFFF0000F		# must be on a segment boundary
	jnz	doerr
	shr	eax,4
	cmp	ax,cx			# compare base memory segment
	jne	doerr
	cmp	dword ptr fs:boot_ld_mlength[di],SECTSIZE
	jne	doerr			# has to have a size of one sector
	mov	gs,ax
	mov	ax,word ptr gs:[VGA_OFFSET]
	mov	[vgamode],ax		# save video mode for later

/*
 * Determine the kernel setup entry address. It has to immediately follow
 * the floppy boot sector and has to contain the proper signature. Also
 * check that we have enough free memory in the setup segment to place the
 * kernel heap into it.
 */

	mov	si,offset recerr
	mov	al,SETUPNUM + LOADER_TAG
	call	ldrfnd			# find load record for kernel
	jc	doerr			# setup module
	call	ldradr
	jc	doerr
	test	eax,0xFFF0000F		# module address must be reach-
	jnz	doerr			# able and on paragraph boundary
	shr	eax,4			# get segment of setup code
	mov	gs,ax
	sub	ax,SECTSIZE / 16
	cmp	cx,ax			# has to follow the floppy
	jne	doerr			# boot sector

	mov	si,offset seterr
	cmp	dword ptr gs:[su_magic],SETUP_MAGIC
	jne	doerr				# check magic number
	mov	si,offset olderr
	cmp	word ptr gs:[su_version],SETUP_VERSION_OLD
	jb	doerr				# check version number
	mov	ax,word ptr gs:[su_sys_seg]
	mov	[krnseg],ax			# get kernel segment

	mov	si,offset recerr
	mov	edx,fs:boot_ld_mlength[di]
	sub	edx,fs:boot_ld_ilength[di]
	jbe	doerr
	cmp	edx,LINUX_HEAP			# check if its enough for
	jb	doerr				# the heap

/*
 * The command line has been placed into our memory area by the bootrom
 * already, so that we can determine its offset from the beginning of the
 * floppy boot sector. Note that the segment of the floppy boot sector
 * should still be in CX.
 */

	mov	si,offset recerr
	mov	al,CMDLNUM + LOADER_TAG
	call	ldrfnd			# find load record for command line
	jc	doerr
	call	ldradr			# get address of command line
	jc	doerr
	mov	bx,cx
	shl	ecx,4			# CX still contains the segment of
	and	ecx,0x000FFFF0		# the floppy boot sector
	sub	eax,ecx			# compute offset to command line
	jbe	doerr
	cmp	eax,0x00010000 - CMDL_SIZE
	ja	doerr			# should not be larger than 64kB
	mov	[cmdofs],ax		# save offset ot command line
	cmp	dword ptr fs:boot_ld_mlength[di],CMDL_SIZE
	jb	doerr			# check maximum size of command line

	push	ds
	mov	si,ax
	mov	ds,bx
	call	strlen			# determine actual length of string
	cmp	cx,CMDL_SIZE - 2
	jbe	1f			# command line is not allowed to be
	mov	cx,CMDL_SIZE - 2	# longer than maximum
1:	mov	bx,cx
	mov	byte ptr [bx + si],0	# terminate command line with zero
	pop	ds
	mov	[cmdlen],cx		# save command line length

.ifgt ASM_DEBUG
	mov	dx,ax
	mov	si,offset cmdmsg
	call	prnstr
	mov	ax,dx			# let the user know about the
	call	prnwrd			# segment and size of command line
	mov	al,0x28
	call	prnchr
	mov	ax,cx
	call	prnwrd
	mov	al,0x29
	call	prnchr
	call	prcrlf
.endif

/*
 * Now check that the kernel has been loaded at the correct place and get
 * the kernel size. 
 */

	mov	si,offset recerr
	mov	al,KERNELNUM + LOADER_TAG
	call	ldrfnd				# find load record for kernel
	jc	doerr				# image
	call	ldradr
	jc	doerr
	cmp	eax,KERNEL_LD_HIGH		# kernel has to be loaded into
	jne	doerr				# high memory
	mov	bl,fs:boot_ld_length[di]
	test	bl,0xF0
	jz	doerr
	and	bx,0x000F			# get offset of vendor infor-
	shl	bx,2				# mation within load record
	mov	bl,fs:boot_ld_krnflags[bx + di]	# get kernel vendor flags
	mov	[krnflg],bl
	mov	eax,fs:boot_ld_ilength[di]	# get kernel size
	test	bl,KRN_LOW			# size doesnt matter if the
	jz	1f				# kernel remains loaded into
	mov	dx,LINUX_MEM			# high memory
	sub	dx,[krnseg]			# compute number of available
	jbe	doerr				# segments
	movzx32	edx,dx				# convert number of segments
 	shl	edx,4				# into number of bytes
	cmp	eax,edx				# check that the kernel is
	ja	doerr				# not too large for low memory
1:	mov	[krnsiz],eax

/*
 * Find a preloaded ram disk image. If the image is not already loaded at 
 * a fixed address or at the end of physical memory, copy it there.
 */

	mov	al,RAMDISKNUM + LOADER_TAG
	call	ldrfnd				# find load record for ramdisk
	jc	2f				# no ramdisk image found
	mov	eax,fs:boot_ld_mlength[di]
	mov	[rdsize],eax			# save size of ramdisk image

	call	ldradr				# get address of ramdisk image
	mov	edx,eax
	mov	bl,fs:boot_ld_length[di]
	test	bl,0xF0
	jz	doerr
	and	bx,0x000F			# get offset of vendor infor-
	shl	bx,2				# mation within load record
	cmp	byte ptr fs:boot_ld_rdflags[bx + di],RD_AUTO
	jne	1f				# accept fixed address
						# otherwise relocate ramdisk to
	call	getext				# end of physical memory
	add	eax,0x00000400			# add size of conventional
	shl	eax,10				# memory and multiply by 1024
	sub	eax,[rdsize]			# compute new start of ramdisk
	mov	ebx,eax				# needs to be relocated to end
	xchg	eax,edx				# of memory
	mov	ecx,fs:boot_ld_ilength[di]
	call	movmem				# move ramdisk
	mov	si,offset moverr
	jc	doerr				# check if error

1:	mov	[ramdisk],edx			# save ramdisk address

.ifgt ASM_DEBUG
	mov	si,offset rdmsg
	call	prnstr
	mov	eax,edx
	call	prndwd				# let the user know
	mov	al,0x28
	call	prnchr
	mov	eax,[rdsize]
	call	prndwd
	mov	ax,0x29
	call	prnchr
	call	prcrlf
.endif

/*
 * The bootrom moved all modules required to be loaded into conventional
 * memory below 0x80000, because this is required by the netboot spec.
 * However, the linux kernel expects the command line and the stack in a
 * segment starting at 0x90000. Also, the 32-bit kernel has been loaded
 * into high memory by the bootrom. Some kernels dont like to be started
 * high, and therefore have to be copied into low memory. The memory area
 * from 0x10000 up to 0x90000 is reserved for the Linux kernel.
 * The linux kernel expects the floppy boot sector to start at 0x90000, the
 * 16-bit startup code at 0x90200 and the heap, command line and stack
 * thereafter. However, everything has to be below 0x9A000 (at least for
 * SETUP versions below 2.02).
 * With the code above we have made sure that we really have enough base
 * memory up to 0x9A000. Also it has been checked that the floppy boot
 * sector, the kernel setup, the command line and this loader program are
 * within a size limit of 0x0A000 bytes. And since we dont need the boot-
 * rom anymore (all error conditions have been checked for) we can now
 * copy everything up to 0x90000, setup the stack there and jump to our
 * own copy.
 * From this point on, it is no longer possible to return to the bootrom.
 */

2:	cli				# disable interrupts during copying
	mov	dx,cs			# and setup stack. we cant use the
	mov	cx,[lowsiz]		# stack during coying because it might
	mov	ax,[lowmem]		# get scrambled by the copying.
	push	ds
	mov	ds,ax
	pop	ax
	mov	bx,LINUX_MEM
	mov	es,bx
	xor	si,si
	xor	di,di			# copy everything up
	rep	movsb
	mov	ss,bx			# set the stack
	mov	sp,HIGH_MEM
	mov	ds,ax
	sti				# it is safe again

	mov	cx,dx
	sub	dx,[lowmem]		# compute new address of this program
	add	dx,bx
	push	dx
	sub	ax,cx			# compute new data segment
	add	dx,ax
	mov	ds,dx			# set new segment registers
	mov	es,dx
	mov	ax,offset 3f
	push	ax
	lret				# call new copy

3:

/*
 * At this point the memory layout should look like this:
 *
 * 0x090000-0x0901FF    0.5 kB   linux floppy boot sector
 * 0x090200-0x0941FF   16.0 kB   kernel setup, including heap
 * 0x094200-0x0949FF    2.0 kB   kernel command line
 * 0x094C00-0x098BFF   16.0 kB   primary boot loader - this program
 * 0x098C00-0x099FFF    5.5 kB   stack
 * 0x100000-0x1FFFFF 1024.0 kB	kernel
 * 0xxxxxxx-0xxxxxxx    x.x kB   ramdisk
 *
 * The remaining part of this program just deals with setting up the
 * command line, then copy the kernel where it belongs and call it.
 */

.ifgt ASM_DEBUG_VERBOSE
	mov	ax,[cmdlen]
	call	prnwrd
	mov	al,0x20
	call	prnchr
	push	ds
	mov	si,[cmdofs]
	mov	ax,LINUX_MEM
	mov	ds,ax
	call	prnstr			# print the command line for
	pop	ds			# debugging
	call	prcrlf
.endif

/*
 * Now determine the name of the root directory on the server by looking
 * into the BOOTP record. If a boot image file name was found, only leave
 * the directory name by removing everything after the last slash. There
 * is also the possibility to define a root directory name in the vendor
 * area of the BOOTP record, so check for that also.
 * Newer kernels also need to specify the IP address of the NFS server
 * as part of the root directory path name. We will use the IP address
 * of the TFTP or BOOTP server in case the root directory name has been
 * built out of the boot image file name. However, if we can find a root
 * directory option in the BOOTP record, we assume that the directory name
 * string found in that option contains an NFS server IP address, if appli-
 * cable.
 */

	cld
	push	bp
	xor	dx,dx
	mov	bp,offset pathbuf
	test	byte ptr [krnflg],KRN_NFS_IP
	jz	7f			# do we have to have an NFS server addr
	mov	di,bp
	lea	si,bootp_siaddr[bootpbuf]
	call	cvtadr			# put address into destination string
	mov	al,0x3A			# add ":"
	call	cvtchr
	mov	bp,di			# save pointer to end of IP address

7:	mov	al,VEND_RFC_BOOTFILE
	call	btptag			# check for boot image file name
	jcxz	3f
	mov	si,di			# get boot image file name from BOOTP
	jmp	6f			# record

3:	xor	bx,bx
	lea	si,bootp_file[bootpbuf]
	call	strlen			# determine length of file name
	jcxz	2f			# no file name, dont touch path
6:	mov	di,bp
	add	cx,dx
	mov	dx,MAX_PATH_LEN - 2	# keep space in path name for
	cmp	cx,dx			# trailing 0
	jbe	1f
	mov	cx,dx			# get length of path name
1:	mov	ax,bp
	sub	ax,offset pathbuf	# compute length of directory name
	sub	cx,ax			# without IP address string
	mov	bx,cx			# save length for later
	rep	movsb			# copy path name to its new location
	xor	al,al
	stosb				# save trailling 0

2:	xor	dx,dx
	or	bx,bx			# check if we have a file name
	jz	4f
	std
	mov	di,offset pathbuf
	mov	ax,bp
	sub	ax,di
	add	bx,ax			# compute length of whole string
	mov	cx,bx			# including IP address
	add	di,bx			# point DI to end of string
	mov	al,0x2F			# search backwards for last occurrence
	repne	scasb			# of a slash
	jnz	4f			# not found -> no path
	mov	byte ptr [di + 1],0	# replace the slash with 0
	mov	dx,cx
	inc	dx
4:	mov	[pathlen],dx		# store length of directory name

	cld
	mov	al,VEND_RFC_ROOTPATH	# there is still another possibility:
	call	btptag			# get the directory name from vendor
	jcxz	9f			# field of bootp record
	mov	dx,cx			# in that case we can overwrite the
	mov	si,di			# IP address previously added, so
	mov	di,offset pathbuf	# copy the directory name from the
	rep	movsb			# bootp record directly at the be-
	xor	al,al			# ginning of the path name buffer
	stosb				# and terminate the string with 0
	mov	[pathlen],dx
9:	pop	bp

.ifgt ASM_DEBUG_VERBOSE
	mov	ax,[pathlen]
	call	prnwrd
	mov	al,0x20
	call	prnchr
	mov	si,offset pathbuf	# print out the path
	call	prnstr
	call	prcrlf
.endif

/*
 * Build the IP address string by looking into the bootp record. Get
 * all relevant IP addresses out of the record.
 */

	xor	dx,dx
	mov	di,offset ipaddrs
	lea	si,bootp_yiaddr[bootpbuf]
	call	cvtadr			# put address into destination
	mov	al,0x3A			# string add ":"
	call	cvtchr
	add	si,_I(bootp_siaddr - bootp_yiaddr)
	call	cvtadr			# put server address into dest
	mov	al,0x3A			# string add ":"
	call	cvtchr

	push	di
	mov	al,VEND_RFC_ROUTER	# we have to take the gateway address
	call	btptag			# from the vendor info area
	mov	si,di
	pop	di
	jcxz	1f			# no netmask found
	call	cvtadr			# put gateway address into dest string
1:	mov	al,0x3A			# add ":"
	call	cvtchr

	push	di
	mov	al,VEND_RFC_SUBNET	# we have to take the netmask from the
	call	btptag			# vendor info area
	mov	si,di
	pop	di
	jcxz	2f			# no netmask found
	call	cvtadr			# put netmask address into dest string
2:	mov	al,0x3A			# add ":"
	call	cvtchr

	push	di
	mov	al,VEND_RFC_HNAME	# get the host name from the vendor
	call	btptag			# info area
	mov	si,di
	pop	di
	jcxz	3f			# no host name found
	add	dx,cx			# update string length
	rep	movsb			# move host name into dest string

3:	push	di
	mov	al,VEND_RFC_RDEV	# get the name for the network device
	call	btptag			# by which to reach the root FS
	mov	si,di
	pop	di
	jcxz	4f			# no device name found
	mov	al,0x3A			# only add ":" if there is a device name
	call	cvtchr
	add	dx,cx			# update string length
	rep	movsb			# move device name into dest string

4:	xor	al,al			# terminate string with 0
	stosb
	mov	[ipadrlen],dx		# save string length

.ifgt ASM_DEBUG_VERBOSE
	mov	ax,dx
	call	prnwrd
	mov	al,0x20
	call	prnchr
	mov	si,offset ipaddrs	# let the user know
	call	prnstr
	call	prcrlf
.endif

/*
 * Process the command line by inserting the root directory name and the
 * IP address string.
 */

	mov	si,offset rtstr
	mov	di,offset pathbuf
	mov	ax,[pathlen]		# first handle root directory name
	or	ax,ax
	jnz	1f
	mov	di,ax
1:	call	procv			# setup command line

	test	byte ptr [krnflg],KRN_USE_IP
	jz	4f			# use "nfsaddrs" command line option?
	mov	si,offset ipstr
	mov	di,offset ipaddrs
	mov	ax,[ipadrlen]		# then handle the IP address string
	or	ax,ax
	jnz	2f
	mov	di,ax
2:	call	procv			# setup command line
	jmp	5f

4:	mov	si,offset adrstr
	mov	di,offset ipaddrs
	mov	ax,[ipadrlen]		# then handle the IP address string
	or	ax,ax
	jnz	3f
	mov	di,ax
3:	call	procv			# setup command line

5:
.ifgt ASM_DEBUG_VERBOSE
	mov	ax,[cmdlen]
	call	prnwrd
	mov	al,0x20
	call	prnchr
	push	ds
	mov	si,[cmdofs]
	mov	ax,LINUX_MEM
	mov	ds,ax			# Print the new command line for
	call	prnstr			# debugging
	pop	ds
	call	prcrlf
.endif

/*
 * Add any additional command line extensions from the BOOTP record and
 * check for a VGA mode command line option.
 */

	mov	al,VEND_RFC_CMDAD	# check for application vendor tag
	call	btptag
	jcxz	6f			# tag not available
	mov	si,offset nulstr
	mov	bx,cx
	call	addarg			# add string to end of command line
6:	call	vga			# process "vga=" argument

.ifgt ASM_DEBUG_VERBOSE
	mov	ax,[cmdlen]
	call	prnwrd
	mov	al,0x20
	call	prnchr
	push	ds
	mov	si,[cmdofs]
	mov	ax,LINUX_MEM
	mov	ds,ax			# Print the new command line for
	call	prnstr			# debugging
	pop	ds
	call	prcrlf
.endif

/*
 * Setup the kernel header by setting the pointer to the command line,
 * the load flags, the ramdisk and the kernel start address.
 */

	mov	ax,LINUX_MEM
	mov	gs,ax			# Let GS:SI point to kernel setup
	mov	si,SECTSIZE		# header, and GS:0 to floppy boot
	mov	eax,[ramdisk]		# sector
	mov	dword ptr gs:su_ramdisk_start[si],eax
	mov	eax,[rdsize]
	mov	dword ptr gs:su_ramdisk_size[si],eax
	mov	byte ptr gs:su_type_of_loader[si],SETUP_MY_TYPE
	mov	byte ptr gs:su_load_flags[si],SETUP_LDFLAGS_HEAP
	mov	ebx,KERNEL_LD_LOW
	test	byte ptr [krnflg],KRN_LOW
	jnz	1f
	or	byte ptr gs:su_load_flags[si],SETUP_LDFLAGS_HIGH
	mov	ebx,KERNEL_LD_HIGH
1:	mov	dword ptr gs:su_32_start[si],ebx

	cmp	word ptr gs:su_version[si],SETUP_VERSION_NEW
	jb	2f
	movzx32	eax,word ptr [cmdofs]	# new setup header uses linear address
 	add	eax,LINUX_MEM * 16	# for command line
	mov	dword ptr gs:su_cmdl_ptr[si],eax
	jmp	3f

2:	mov	ax,word ptr [cmdofs]	# old setup header uses offset
	mov	word ptr gs:[CL_MAGIC_ADDR],CL_MAGIC
	mov	word ptr gs:[CL_OFFSET],ax

3:	mov	ax,[vgamode]
	mov	word ptr gs:[VGA_OFFSET],ax

/*
 * The Linux kernel occupies up to 512 kB of memory. Since the bootrom needs
 * the space above 0x80000 this doesnt leave enough memory left for this boot
 * loader if the kernel gets loaded into low memory. Therefore, the bootrom
 * loads the kernel into high memory in any case. Then after we have made
 * sure that we dont have to return to the bootrom, we copied ourselves up
 * into the bootrom area. Now we can copy the kernel down into low memory if
 * required. Note that we have the final kernel address in EBX already.
 */

	test	byte ptr [krnflg],KRN_LOW
	jz	9f
	mov	eax,KERNEL_LD_HIGH
	movzx32	ebx,word ptr [krnseg]
	shl	ebx,4
	mov	ecx,[krnsiz]		# copy the kernel from high to
	call	movmem			# low memory

/*
 * Finally we can start the game by calling the kernel setup.
 */

9:
.ifgt ASM_DEBUG
	mov	si,offset dbgmsg
	call	prnstr
	call	prcrlf
.endif
.ifgt ASM_FREEZE_AFTER_INIT
1:	jmp	1b			# this is a feature, not a bug...
.else
.ifgt ASM_KEYPRESS_AFTER_INIT
	mov	si,offset keymsg
	call	prnstr
	xor	ah,ah
	int	INT_KBD
	call	prcrlf
.endif
	mov	ax,SETUP_SEG		# set segment registers
	mov	ds,ax
	mov	es,ax
	xor	ax,ax
	mov	fs,ax
	mov	gs,ax
	ljmp	SETUP_SEG,0		# finally call the kernel
.endif



/*
 *====================================================================
 *
 * Convert an IP address into a string. The length of the string
 * gets accumulated.
 * Input:  DS:SI -  Pointer to IP address
 *         ES:DI -  Target string buffer
 *         DX    -  String length up to character pointed to by ES:DI
 * Output: ES:DI -  First character after converted string
 *         DX    -  New total length of string
 * Changed registers: AX, DX, DI
 */
cvtadr:	push	bx
	mov	ax,word ptr [si + 0]
	or	ax,word ptr [si + 2]	# if address is 0.0.0.0, dont
	jz	9f			# convert it

	xor	bx,bx
1:	mov	al,byte ptr [bx + si]
	call	cvtdec			# convert each byte in turn into a
	cmp	bx,3			# decimal number
	jae	9f			# abort if at last byte of address
	mov	al,0x2E			# add "."
	call	cvtchr
	inc	bx			# proceed with next byte
	jmp	1b

9:	pop	bx
	ret



/*
 *====================================================================
 *
 * Convert a byte into a decimal number string. The string length
 * gets accumulated.
 * Input:  AL    -  Byte value
 *         ES:DI -  Target string buffer
 *         DX    -  String length up to character pointed to by ES:DI
 * Output: ES:DI -  First character after converted string
 *         DX    -  New total length of string
 * Changed registers: AX, DX, DI
 */
cvtdec:	push	si
	push	dx
	movzx32	eax,al
	mov	dl,10			# convert byte value into ASCII
	call	cvtnum			# number
	pop	dx

1:	lodsb
	or	al,al			# check for end of string
	jz	9f
	call	cvtchr			# put each character of ASCII number
	jmp	1b			# string into target buffer

9:	pop	si
	ret



/*
 *====================================================================
 *
 * Put a character into the IP Address string buffer
 * Input:  AL    -  Character to put into buffer
 *         ES:DI -  Pointer to end of string
 *         DX    -  Current string length
 * Output: ES:DI -  New end of string pointer
 *         DX    -  New string length
 * Changed registers: DX, DI
 */
cvtchr:	cmp	dx,MAX_ADDRS_LEN	# dont exceed limit of address string
	jae	9f			# buffer
	stosb
	inc	dx
9:	ret



/*
 *====================================================================
 *
 * Process an argument on the command line. This routine handles
 * basically three cases:
 *   - the argument value is "rom", which means to use the value
 *     delivered by the boot rom
 *   - the argument value is "kernel", which means to just remove
 *     the argument from the command line and then let the kernel
 *     use its defaults
 *   - the argument value contains neither "rom" nor "kernel", so
 *     dont touch it.
 *
 * Input:  DS:SI -  Pointer to name of option to look for in command line
 *         DS:DI -  Pointer to bootrom value, or NULL if no value
 * Output: none
 * Changed registers: AX, BX, CX, DX, SI, DI, ES
 */
procv:	push	bp
	push	es
	push	di
	mov	ax,LINUX_MEM
	mov	es,ax
	mov	di,[cmdofs]
	mov	dx,di			# load pointer to command line and
	call	strstr			# find option name
	jcxz	3f			# option not found in cmd line
	mov	bx,di			#    ES:BX points to option name
	add	di,cx			#    ES:DI points to argument
	add	dx,[cmdlen]		#    DX    contains remaining cmd line
	sub	dx,di
	jle	2f			# length of remaining cmd line is zero
	mov	cx,dx
	mov	dx,di
	mov	al,0x20			# find end of argument (terminated by
	repne	scasb			# blank) or end of string (e.g. CX=0)
	jcxz	1f			# argument goes to end of string
	dec	di
1:	sub	di,dx
	xchg	dx,di			# compute length of argument in DX
	jg	4f			# argument length is greater than zero

2:	mov	dx,si
	mov	di,bx			# if we got here, we found the option
	call	rmarg			# name but no argument, so just remove
	mov	si,dx			# the option from the cmd line.
3:	xor	dx,dx
4:	pop	bp			# get pointer to boot rom value

/*
 * At this point the registers are allocated as follows:
 *      DS:SI  points to name of option
 *      DS:BP  points to boot rom value
 *      ES:BX  points to option name in command line
 *      ES:DI  points to argument in command line
 *      DX     contains length of argument; if 0, BX and DI are invalid
 *
 * After all that we have a couple of choices for constructing the kernel
 * command line:
 *
 * argument not given in command line:
 *   -  if there is no value from the boot rom, just do nothing and let
 *      the kernel decide
 *   -  if there is a value from the boot rom, add a new argument at the
 *      end of the command line using the boot rom value
 *
 * argument given in command line:
 *   -  if the value of the argument is not empty and not "rom" or "kernel",
 *      just do nothing
 *   -  if the value of the argument is "rom", then remove the argument
 *      from the command line and add a new one with the boot rom value, but
 *      only if a boot rom value is available
 *   -  if the value of the argument is "kernel", then remove the argument
 *      from the command line and do nothing, thus letting the kernel decide
 */

	or	dx,dx			# check if argument found in command
	jnz	0f			# line

	or	bp,bp
	jz	9f			# if no boot rom value is available,
	mov	al,ds:[bp]		# e.g. the string pointer is NULL or
	or	al,al			# the string length is zero, then
	jz	9f			# just return and do nothing.

	mov	di,bp
	xor	bx,bx			# if not, add new option with boot rom
	call	addarg			# argument to end of command line
9:	pop	es
	pop	bp
	ret

/*
 * Original command line contains the option we are looking for. Check whether
 * the argument is one of the strings "rom" or "kernel"
 */

0:	cld
	push	si
	mov	ax,di
	mov	cx,dx
	cmp	cx,offset romend - offset romstr
	jne	5f
	mov	si,offset romstr
	repe	cmpsb			# compare for "rom"
	mov	di,ax
	jz	7f
5:	mov	cx,dx
	cmp	cx,offset kerend - offset kerstr
	jne	6f
	mov	si,offset kerstr
	repe	cmpsb			# compare for "kernel"
	mov	di,ax
	jz	8f
6:	pop	si			# not found --> do nothing
	jmp	9b

/*
 * the argument is "rom", so remove the existing argument from the
 * command line, and add the boot rom value
 */

7:	mov	di,bx
	call	rmarg			# remove old option containing
	pop	si			# "rom" argument
	mov	di,bp

	or	di,di
	jz	9b			# if no boot rom value is available,
	mov	al,[di]			# e.g. the string pointer is NULL or
	or	al,al			# the string length is zero, then
	jz	9b			# just return and do nothing.
	xor	bx,bx
	call	addarg			# add new option with boot rom value
	jmp	9b			# as argument

/*
 * the argument is "kernel", so just remove the existing argument from
 * the command line and let the kernel decide by itself.
 */

8:	mov	di,bx
	call	rmarg			# remove old option containing
	pop	si			# "kernel" argument
	jmp	9b



/*
 *====================================================================
 *
 * Add a new option to the end of the command line.
 * Input:  DS:SI -  Pointer to name of command line option
 *         DS:DI -  Pointer to argument value
 *         BX    -  Length of argument value, or 0 if zero-terminated string
 * Output: ES:DI -  Pointer to trailing zero of command line
 *         AX    -  New length of command line
 * Changed registers: AX, SI, DI, ES
 */
addarg:	push	cx
	push	dx
	call	strlen			# first compute the new length of
	mov	dx,cx			# the command line. if the new
	or	bx,bx			# argument will not fit into the
	mov	cx,bx			# memory provided, then just return
	jnz	5f			# and leave the command line
	xchg	si,di			# unchanged.
	call	strlen
	xchg	si,di
5:	add	cx,dx
	pop	dx
	add	cx,[cmdlen]
	inc	cx
	cmp	cx,CMDL_SIZE		# check size of new command line
	pop	cx
	jae	9f

	cld
	push	bx
	push	cx
	push	di
	mov	cx,bx
	mov	ax,LINUX_MEM
	mov	es,ax
	mov	di,[cmdofs]
	mov	bx,di
	add	di,[cmdlen]
	mov	al,0x20			# add a blank
	stosb
1:	lodsb				# add the option name
	or	al,al
	jz	2f
	stosb
	jmp	1b
2:	pop	si			# load the argument pointer
3:	lodsb				# add the argument
	or	al,al
	jz	4f
	stosb
	loop	3b
4:	xor	al,al			# add trailing 0
	stosb
	dec	di
	mov	ax,di
	sub	ax,bx
	mov	[cmdlen],ax		# adjust length of command line
	pop	cx
	pop	bx
9:	ret



/*
 *====================================================================
 *
 * Remove an argument from the command line.
 * Input:  ES:DI -  Pointer to argument name within command line
 * Output: ES:DI -  Pointer to trailing 0 of command line
 *         AX    -  New length of command line
 * Changed registers: AX, SI, DI, ES
 */
rmarg:	cld
	push	bx
	push	cx
	push	dx
	push	ds
	mov	si,[cmdofs]		# get beginning of command line
	mov	ax,es
	mov	ds,ax
	mov	bx,si
	mov	si,di			# get remaining length of cmd line
	call	strlen			# find end of argument in command line
	mov	ax,0x20			# DS:SI points to first character after
	repne	scasb			#       the argument string
	dec	di			# ES:DI points to start of argument
	xchg	si,di			# ES:BX points to start of cmd line
	or	cx,cx			# argument is not at the end of the
	jnz	2f			# command line

1:	cmp	di,bx
	je	4f			# empty command line
	dec	di			# remove leading blanks
	cmp	byte ptr es:[di],0x20
	je	1b
	inc	di			# point to first blank after preceeding
	jmp	4f			# argument and make it end of string

2:	lodsb
	cmp	al,0x20
	je	2b			# remove trailing blanks
3:	or	al,al
	jz	1b			# end of command line... remove all
	stosb				# trailing blanks. Otherwise copy
	lodsb				# the remainder of the command line
	jmp	3b			# over the argument

4:	xor	al,al
	stosb				# add trailing 0
	pop	ds
	pop	dx
	pop	cx
	dec	di			# point to trailing 0
	mov	ax,di
	sub	ax,bx			# adjust length of command line
	mov	[cmdlen],ax
	pop	bx
	ret



/*
 *====================================================================
 *
 * Handle any "vga=" argument that might be on the command line
 * Input:  none
 * Output: none
 * Changed registers: AX, BX, CX, DX, SI, DI
 */
vga:	push	es
	mov	bx,[vgamode]
	mov	ax,LINUX_MEM
	mov	es,ax
0:	mov	di,[cmdofs]
	mov	si,offset vgastr
	call	strstr			# ES:DI  = strstr(cmdptr,"vga=")
	jcxz	9f			# not found

/*
 * Check if the argument to the "vga=" option does contain any of the
 * special key words, or a hex or decimal number.
 */
	
	push	di
 	add	di,cx			# ES:DI += strlen("vga=")
	mov	ax,di

	cld
	mov	bx,VGA_ASK
	mov	cx,offset askend - offset askstr
	mov	si,offset askstr	# strncmp(ES:DI,"ASK",3)
	repe	cmpsb
	mov	di,ax
	jz	8f

	mov	bx,VGA_EXTENDED
	mov	cx,offset extend - offset extstr
	mov	si,offset extstr	# strncmp(ES:DI,"EXTENDED",8)
	repe	cmpsb
	mov	di,ax
	jz	8f

	mov	bx,VGA_NORMAL
	mov	cx,offset nrmend - offset nrmstr
	mov	si,offset nrmstr	# strncmp(ES:DI,"NORMAL",6)
	repe	cmpsb
	mov	di,ax
	jz	8f

	xor	bx,bx
	mov	cx,offset hexend - offset hexstr
	mov	si,offset hexstr	# strncmp(ES:DI,"0x",2)
	repe	cmpsb
	mov	di,ax
	jz	5f
	jmp	6f

8:	pop	di
	call	rmarg			# remove argument
	jmp	0b			# handle next argument (if any)

/*
 * Return after storing the VGA mode and printing out the result
 */

9:	pop	es
	mov	[vgamode],bx

.ifgt ASM_DEBUG_VERBOSE
	mov	si,offset vgastr
	call	prnstr
	mov	ax,bx
	call	prnwrd
	call	prcrlf
.endif

	ret


/*
 * Decode a hex number
 */

5:	xor	dh,dh
	inc	di
3:	inc	di			# ES:DI++
	mov	dl,es:[di]
	sub	dl,0x30			# if (*ES:DI < "0") break
	jc	8b
	cmp	dl,0x09			# if (*ES:DI > "9") goto nodigit
	jbe	2f
	sub	dl,0x11			# if (*ES:DI < "A") break
	jc	8b
	cmp	dl,0x05			# if (*ES:DI > "F") goto nocapital
	jbe	1f
	sub	dl,0x20			# if (*ES:DI < "a") break
	jc	8b
	cmp	dl,0x05			# if (*ES:DI > "f") break
	ja	8b
1:	add	dl,0x0A
2:	shl	bx,4			# BX = 16*BX + digit
	add	bx,dx
	jmp	3b


/*
 * Decode a decimal number
 */

6:	xor	al,al
	xor	dh,dh
	cmp	byte ptr es:[di],0x2D	# if (*ES:DI == "-") sign++
	jne	2f
	inc	al			# AL = sign
1:	inc	di			# ES:DI++
2:	mov	dl,es:[di]
	sub	dl,0x30			# if (*ES:DI < "0") break
	jc	4f
	cmp	dl,0x09			# if (*ES:DI > "9") break
	ja	4f
	add	bx,bx			# BX = 10*BX + *ES:DI - "0"
	mov	cx,bx
	shl	bx,2
	add	bx,cx
	add	bx,dx
	jmp	1b

4:	or	al,al			# if (sign) BX = -BX
	jz	8b
	neg	bx
	jmp	8b




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


/* Startup signature */
sigmsg:	.byte	0x0D, 0x0A
	.ascii	"Linux Net Boot Image Loader "
	.ascii	VERSION
	.byte	0x0D, 0x0A
	.ascii	"Copyright (C) 1996,1997 G. Kuhlmann and M. Gutschke"
	.byte	0x0D, 0x0A
	.ascii	COPYRIGHT
	.byte	0x0D, 0x0A
	.byte	0x0D, 0x0A
	.byte	0


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


/* Possible option names for command line */
rtstr:	.asciz	NFS_ROOT
adrstr:	.asciz	NFS_ADDRS
ipstr:	.asciz	NFS_IP
vgastr:	.asciz	VGA_ARG

askstr:	.ascii	"ASK"
askend:	.byte	0

extstr:	.ascii	"EXTENDED"
extend:	.byte	0

nrmstr:	.ascii	"NORMAL"
nrmend:	.byte	0

hexstr:	.ascii	"0x"
hexend:	.byte	0

nulstr:	.byte	0


/* Possible arguments for command line options */
romstr:	.ascii	"rom"
romend:	.byte	0

kerstr:	.ascii	"kernel"
kerend:	.byte	0


/* Error messages */
memerr:	.asciz	"Not enough memory"
recerr:	.asciz	"Error in load record data"
seterr:	.asciz	"Error in kernel setup header"
olderr:	.asciz	"Linux kernel too old"
moverr:	.asciz	"Failed to move ramdisk image to end of memory"

.ifgt ASM_DEBUG
/* Some debugging messages */
memmsg:	.asciz	"Base memory = "
extmsg:	.asciz	"Extended memory = "
lowmsg:	.asciz	"Low memory = "
cmdmsg:	.asciz	"Command line offset = "
rdmsg:	.asciz	"Ramdisk start = "
.ifgt ASM_KEYPRESS_AFTER_INIT
keymsg:	.asciz	"Press any key to continue"
.endif
.ifgt ASM_FREEZE_AFTER_INIT
dbgmsg:	.asciz	"Freezing!"
.else
dbgmsg:	.asciz	"Calling Linux kernel"
.endif
.endif




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

	.extern	bootpbuf		# space to hold BOOTP record
	.extern	header			# pointer to load record header

	.lcomm	lowmem,2		# lowest used segment
	.lcomm	lowsiz,2		# used memory size in lower memory

	.lcomm	ramdisk,4		# pointer to start of ramdisk (32bit)
	.lcomm	rdsize,4		# size of ramdisk

	.lcomm	cmdofs,2		# pointer to start of command line
	.lcomm	cmdlen,2		# length of command line

	.lcomm	vgamode,2		# VGA mode
	.lcomm	krnsiz,4		# size of high-loaded kernel image
	.lcomm	krnseg,2		# segment of low-loaded kernel image
	.lcomm	krnflg,1		# kernel vendor flag

	.align	2

	.lcomm	ipadrlen,2		# length of IP address string
	.lcomm	ipaddrs,MAX_ADDRS_LEN	# space to store IP addrs string

	.lcomm	pathlen,2		# length of path string
	.lcomm	pathbuf,MAX_PATH_LEN	# space to store path name



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

