/*
 **************************************************************************
 *
 * Boot-ROM-Code to load an operating system across a TCP/IP network.
 *
 * Module:  rom.S86
 * Purpose: Decompress the rom image and copy everything into place
 * Entries: Called by BIOS at offset 0x0003, getbyte, putbyte
 *
 **************************************************************************
 *
 * 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: rom.S86,v 1.12 2007/01/06 18:30:47 gkminix Exp $
 *
 **************************************************************************
 */

#include <common.i86>
#include <romconfig.i86>
#include <system/bootrom.i86>
#include <system/bios.i86>
#include <system/pnp.i86>
#include <system/pci.i86>
#include <system/pmm.i86>
#include <pxe/loader.i86>
#include <pxe/romid.i86>
#include "ldram.i86"

.file __FILE__
.line __LINE__



/*
 **************************************************************************
 *
 * Equates for this module:
 */
#define BBSWAIT		3		/* number of seconds to wait */
#define COMPADR		FLOPMEM		/* segment for compressed image */
#define LOADADR		FLOPEND		/* segment to load the boot code */
#define LOADEND		OSENDMEM	/* end segment of this boot code */
#define BOOTLOAD	0x7C00		/* boot load area in segment 0 */
#define MINSTK		0x2000		/* Minimum stack pointer */

#if (LOADEND - LOADADR) < 0x1000
# error Invalid boot loader size
#endif



/*
 **************************************************************************
 *
 * Definitions for accessing the high memory area using the BIOS
 */
#define GDTSRCLEN	0x0010		/* source seg length */
#define GDTSRCADR	0x0012		/* source seg linear address */
#define GDTSRCRIGHT	0x0015		/* source seg access rights */
#define GDTSRCHIADR	0x0017		/* source seg linear addr high byte */
#define GDTDESTLEN	0x0018		/* destination seg length */
#define GDTDESTADR	0x001A		/* destination seg linear address */
#define GDTDESTRIGHT	0x001D		/* destination seg access rights */
#define GDTDESTHIADR	0x001F		/* dest seg linear addr high byte */

#define GDTSTRUCTLEN	0x0030		/* length of GDT BIOS structure */
#define GDTACCESS	0x93		/* segment access rights */



/*
 **************************************************************************
 *
 * Definitions used to setup the PnP option ROM expansion header as
 * defined per PnP BIOS Specification, Version 1.0A.
 *
 * Vendor and Device ID: This is only required for PnP compatible operating
 *   systems to load the necessary drivers. For the bootrom, we use 0 for
 *   both values. If it really is required, makerom can add it lateron.
 */
#define PNP_VENDID	0		/* vendor ID not used */
#define PNP_DEVID	0		/* device ID not used */


/*
 * Device Indicators:
 *   Bit 7 = 1  -->  ROM supports DDIM (if selected)
 *   Bit 6 = 0  -->  ROM may not be shadowed in RAM
 *   Bit 5 = 0  -->  ROM is not read cacheable
 *   Bit 4 = 1  -->  ROM is only required if this device is selected for boot
 *   Bit 3 = 0  -->  reserved
 *   Bit 2 = 1  -->  this is an IPL device
 *   Bit 1 = 0  -->  not an input device
 *   Bit 0 = 0  -->  not an output device
 */
#ifdef NODDIM
# define PNP_DEVIND	0b00010100	/* device indicators */
#else
# define PNP_DEVIND	0b10010100	/* device indicators */
#endif


/*
 * Device Type Codes: These are used by the BIOS to prioritize the boot
 *   devices. According to the specs, the base type value should be 2
 *   to identify a network controller. However, some older BIOS (mainly
 *   Award) are not able to detect and boot from anything with a base
 *   type value larger than 1 (disk controller). This is why we use a
 *   value of 0 here.
 */
#define PNP_BASETYPE	0
#define PNP_SUBTYPE	0
#define PNP_IFTYPE	0



/*
 **************************************************************************
 *
 * Definitions used to setup the PCI data structure.
 *
 * Device Indicator: Set to 0x80 if this is the last PCI structure within
 *   the physical ROM.
 */
#define PCI_DEVIND	0x80		/* device indicator */


/*
 * Code type: We are only able to run on x86 Intel architecture.
 */
#define PCI_CODETYPE	0


/*
 * Device Type Codes: These specify the type of interface this bootrom
 *   is controlling.
 */
#define PCI_BASETYPE	2		/* network interface controller */
#define PCI_SUBTYPE	0		/* ethernet */
#define PCI_IFTYPE	0		/* unused */


/*
 * Revision level of code/data: This is just the major/minor version
 *   number of the netboot bootrom.
 */
#define PCI_REVISION	((VER_MAJOR * 100) + VER_MINOR)



/*
 **************************************************************************
 *
 * Definitions for calling PMM functions. All memory blocks have to have
 * a unique signature. We dont use a number as described in the PMM
 * specification because that would mean to compute it at runtime, and
 * that requires additional memory. Instead, we simply set it to a value
 * which hopefully no one else uses.
 */
#define PMM_RAMSIG	0x6E62676B	/* signature of PMM memory block */
#define PMM_FLAGS	0x02		/* flags for memory allocation */




.line __LINE__
/*
 **************************************************************************
 *
 * The physical rom starts here. At the beginning there is some
 * miscellaneous data.
 */
	.text


	.global	_start
	.global	getbyte
	.global	putbyte

	.extern	inflate


	.word	0xAA55			# rom signature
romlen:	.byte	0			# rom size (filled in by checksum prg)

_start:	jmp	romstart		# the BIOS calls us here

infadr:	.word	DECOMPMEM		# this is for debugging purposes


/*
 * The following variables contain the program version number and some
 * other useful values. Dont change the order of any of these values as
 * the binary patch program depends on them. Especially, the pointer to
 * the PnP structure has to be at offset 0x001A as per the BBS spec.
 * Note on the UNDI rom structure pointer: this structure is only required
 * in the header for using the UNDI driver from the BIOS (called early
 * UNDI API usage in Intels PXE specification). Since this is optional
 * according to the PXE specification, and we dont support it, there is
 * no need to have a UNDI rom structure here. We are just running as a
 * normal IPL, so the structure offset is zero here.
 */

	.org	DATAOFS

	.byte	VER_MAJOR		# major version number
	.byte	VER_MINOR		# minor version number
	.long	0			# serial number
botvec:	.word	0			# offset to bootrom interrupt vector
romsiz:	.long	0			# compressed rom size
	.word	0			# reserved
	.word	0			# pointer to UNDI rom structure
pciofs:	.word	pcihdr			# pointer to PCI data structure
pnpofs:	.word	pnphdr			# pointer to PnP expansion header
	.asciz	NBROMID			# romcheck searches for this string
botflg:	.word	0			# boot flags

mannam:	.asciz	MANUFACTURER		# manufacturer name
prdnam:	.asciz	PRODUCT			# product name
copyrt:	.ascii	", "
	.asciz	COPYRIGHT		# copyright message
crlf:	.byte	0x0D,0x0A,0


/*
 * Define the PCI data structure to make the netboot bootrom compatible
 * with the PCI local bus specification 2.1. This header is actually only
 * needed when the bootrom is physically located on the PCI network card.
 * Note that the order of the class codes has to be reversed compared to
 * what the spec says.
 */

	.align	16
pcihdr:	.ascii	PCI_SIGNATURE		# signature for PCI data structure
	.word	0			# vendor ID (filled in later)
	.word	0			# device ID (filled in later)
	.word	0			# ptr to vital product data (unused)
	.word	PCI_HDRSIZE		# length of PCI data structure
	.byte	0			# structure revision
	.byte	PCI_BASETYPE		# base device type code
	.byte	PCI_SUBTYPE		# device subtype code
	.byte	PCI_IFTYPE		# device programming interface
	.word	0			# rom image length (filled in later)
	.word	PCI_REVISION		# version number of code/data
	.byte	PCI_CODETYPE		# code type
	.byte	PCI_DEVIND		# device indicator
	.word	0			# reserved


/*
 * Define the expansion header to make the netboot bootrom Plug and Play
 * compatible according to the Plug and Play BIOS Specification 1.0A. This
 * will allow the system BIOS to select whether to boot from the network or
 * not, and we dont have to redirect interrupt 18h or 19h by ourselves.
 * Note that the order of the class codes has to be reversed compared to
 * what the spec says.
 */

	.align	16
pnphdr:	.ascii	PNP_SIGNATURE		# signature for PnP expansion header
	.byte	0x01			# structure revision 0.1
	.byte	2			# length of header in 16-byte-blocks
	.word	0			# pointer to next header (0 = none)
	.byte	0			# reserved
	.byte	0			# checksum (filled in later)
	.word	PNP_VENDID		# compressed manufacturer code
	.word	PNP_DEVID		# product number and revision, device ID
	.word	mannam			# ptr to manufacturer string
	.word	prdnam			# ptr to product name
	.byte	PNP_BASETYPE		# base device type code
	.byte	PNP_SUBTYPE		# device subtype code
	.byte	PNP_IFTYPE		# device programming interface
	.byte	PNP_DEVIND		# device indicators
	.word	0			# boot connection vector (unused)
	.word	0			# disconnect vector (unused)
	.word	dorom			# bootstrap entry vector
	.word	0			# reserved
	.word	0			# static resource info vector (unused)


/*
 * Define the new stack pointer
 */
stkptr:	.word	(LOADEND - LOADADR) * 16 - 2
	.word	LOADADR


/*
 * These variables are writable when we are using DDIM. They hold the
 * values required to resize the ROM and to handle interrupt 0x15.
 */

#ifndef NODDIM
oldi15:	.long	0xDEADBEEF		# old interrupt 0x15
newsiz:	.long	0			# new extended memory size
newcks:	.byte	0			# new ROM checksum
#endif



.line __LINE__
/*
 **************************************************************************
 *
 * Now start with the actual boot rom code. The startup code just installs
 * a new bootrom interrupt vector, and then returns to the BIOS. This is
 * necessary so that the BIOS gets a chance to also initialize other installed
 * roms. The bootrom vector is used for the ROM BASIC in original IBM PCs
 * and gets called by the BIOS whenever there is no bootable disk in any
 * of the drives.
 * In case we have a PnP capable BIOS, we dont have to redirect the bootrom
 * vector here, as the BIOS will call us lateron with a far call, not an
 * interrupt. If the BIOS is PnP compatible, this routine gets called with
 * the PCI PFA in AX or the PnP CSN in BX. Save these values for later use.
 * Also, if we can write into this code we are running under the Device
 * Driver Initialization Model (DDIM). In that case, we move the compressed
 * bootrom image into extended memory and can therefore reduce the bootrom
 * image accordingly. This way, more space is provided for other option ROMs
 * in the system. See the BIOS boot specification and PXE specification for
 * a further explanation on DDIM.
 * Unfortunately, neither the BBS nor the PXE spec say anything about what
 * registers are allowed to get changed by this routine. In addition, some
 * BIOS set the stack to 0:0400 instead of 0:7C00, which leaves us about 64
 * bytes of stack space. We therefore have to be very careful about stack
 * space usage.
 */
romstart:

/*
 * First save all registers onto the stack and then switch stacks if
 * necessary.
 */

	call	setstk			# set new stack pointer
	push	ds
	push	es

/*
 * Check if we can write into the bootrom memory area. We can only use
 * CX here. All other registers have to remain unchanged.
 */

	mov	cx,0x00FF
	xchg	cl,cs:[romlen]		# set rom length to 0xFF
	cmp	cl,cs:[romlen]		# check if we could write into 
	je	1f	 		# the ROM area
	mov	cs:[romlen],cl		# restore rom length value
	or	ch,RAMFL_WRITE

/*
 * If another boot rom already occupied the ram data area, we have to
 * return to the BIOS without any further initialization. It is not
 * possible to run two instances of the bootrom. Remember not to change
 * any general-purpose register. Setting the rom size byte to zero is
 * enough. If the rom size is zero, we dont need to compute a bootrom
 * checksum (so dont set RAMFL_CHKSUM), and there is no need to setup
 * the rom size value in the PCI header.
 */

1:	push	ax
	call	checkram		# check if we have been initialized
	pop	ax
	jc	3f

7:	test	ch,RAMFL_WRITE		# if the bootrom is writable, set the
	jz	2f			# rom size to zero
	mov	byte ptr cs:[romlen],0
	mov	word ptr cs:[pciofs],0
	mov	word ptr cs:[pnpofs],0
	and	ch,_NOT(RAMFL_CHKSUM)
2:	xor	ax,ax			# not a boot device
	jmp	9f			# return to BIOS

/*
 * Setup our private data area within the interrupt table. The checksum
 * gets written at the end of this routine.
 */

3:	push	cx
	mov	si,RAM_START
	mov	cx,RAM_SIZE / 2
4:	mov	word ptr [si],0		# fill RAM area with zeroes
	add	si,2
	loop	4b
	pop	cx
	mov	word ptr ds:ram_id[RAM_START],RAM_SIGNATURE
	mov	word ptr ds:ram_pnport[RAM_START],dx
	mov	dx,ax

/*
 * Check for the PnP installation structure. With this we just check if
 * we have been called according to the BBS.
 */

	call	checkpnp		# check for PnP installation structure
	jc	5f
	mov	word ptr ds:ram_pnpstruct[RAM_START + 0],di
	mov	word ptr ds:ram_pnpstruct[RAM_START + 2],es
	test	word ptr cs:[botflg],ROMFLG_NOBBS
	jnz	6f
	or	ch,RAMFL_BBS
	jmp	6f

/*
 * We dont have a PnP BIOS, so we have to redirect the necessary interrupts. Also
 * there is no PCI PFA or PnP CSN available, so set them both to 0xFFFF.
 */

5:	mov	dx,0xFFFF
	mov	bx,dx
	mov	word ptr ds:ram_pnport[RAM_START],0
6:	mov	word ptr ds:ram_pnpcsn[RAM_START],bx
	mov	word ptr ds:ram_pcipfa[RAM_START],dx
	mov	byte ptr ds:ram_flags[RAM_START],ch

/*
 * In case we have no BBS support, we can remove the BEV from the PnP
 * header. This device is not an IPL device in that case. Only change
 * the PnP header if the bootrom is writable. In addition, we need to
 * setup the bootrom interrupt vector.
 */

	test	ch,RAMFL_BBS
	jnz	1f

	mov	si,cs:[botvec]
	mov	ax,ds
	mov	es,ax
	mov	di,_I(ram_oldint) + RAM_START
	mov	bx,offset int18		# set new bootrom interrupt
	call	intset

	test	ch,RAMFL_WRITE
	jz	1f
	push	ds
	mov	ax,cs
	mov	ds,ax
	mov	si,offset pnphdr
	and	byte ptr pnphdr_devind[si],0xFB		# remove IPL bit
	mov	word ptr pnphdr_bev[si],0		# set BEV to zero
	movzx32	ecx,byte ptr pnphdr_size[si]
	shl	ecx,4
	call	dochksum				# compute checksum
	sub	byte ptr pnphdr_chksum[pnphdr],ah
	pop	ds
	or	byte ptr ds:ram_flags[RAM_START],RAMFL_CHKSUM

/*
 * We can now handle DDIM support. For this, we copy the compressed bootrom
 * image into extended memory. If we can use PMM, we can simply copy the
 * compressed bootrom image into the space provided by the PMM allocation
 * routine. Only if no PMM BIOS is available, use interrupt 0x15 for copying.
 * In that case, we also have to redirect interrupt 0x15 so that it will
 * return the size of extended memory less than what we used for the bootrom
 * copy. When using DDIM, we are actually running in RAM, so we can write
 * into variables stored in the code segment. After redirecting the interrupt,
 * set the new ROM size.
 * The PMM specification states that when the Boot Entry Vector is called
 * lateron, no PMM services are available anymore. However, the extended
 * memory contents should be left untouched by PMM so we can still use it,
 * even though its not quite clean, but unfortunately, the PXE specification
 * requires the use of DDIM and PMM if available.
 */

1:

#ifndef NODDIM

	test	byte ptr ds:ram_flags[RAM_START],RAMFL_WRITE
	jz	8f

	or	byte ptr ds:ram_flags[RAM_START],RAMFL_DDIM
	mov	ebx,dword ptr cs:[romsiz]
	or	ebx,ebx			# get size of compressed rom image
	jz	0f
	add	ebx,0x03FF		# and convert it into kB
	shr	ebx,10
	mov	di,cs			# compute linear address of compressed
	shl	edi,4			# bootrom image
	add	edi,offset __text_end + 0x000F
	and	edi,0x000FFFF0
	call	findpmm			# find PMM entry point
	or	eax,eax
	jz	5f			# check if PMM entry point found
	push	PMM_FLAGS		# push flags
 data32	push	PMM_RAMSIG		# push memory block signature
	shl	ebx,6			# convert size into paragraphs
	push	ebx			# push size of memory block
	push	PMM_ALLOCATE		# push PMM function code
	push	cs
	push	offset 2f		# push return address
	push	eax			# push PMM entry point
	lret				# call PMM

2:	add	sp,12			# cleanup stack
	mov	si,dx
	shl	esi,16
	mov	si,ax			# check if we got a valid memory block
	or	esi,esi			# in case the memory allocation routine
	jz	0f			# returned with an error we cant divert
	cmp	esi,0xFFFFFFFF		# to using int 0x15 because thats pro-
	je	0f			# hibited when PMM is available
	mov	dword ptr ds:ram_extadr[RAM_START],esi
	mov	ebx,cs:[romsiz]		# get size of compressed rom image
	add	ebx,0x0003		# convert it into number of double-
	shr	ebx,2			# words
	xor	ax,ax
	mov	ds,ax			# reload big linear segment into DS
3:	dec	bx			# we have to do the copying routine
	js	4f			# by hand (instead of using movsd)
 addr32	mov	eax,[edi + ebx * 4]	# because movsd just uses si/di, not
 addr32	mov	[esi + ebx * 4],eax	# esi/edi.
	jmp	3b

4:	or	byte ptr ds:ram_flags[RAM_START],RAMFL_PMM
	jmp	7f

5:	call	getext
	sub	eax,ebx			# compute new extended memory size
	jae	6f
0:	and	byte ptr ds:ram_flags[RAM_START],_NOT(RAMFL_DDIM)
	jmp	8f

6:	push	eax
	mov	ebx,eax			# compute linear address of new bootrom
	shl	ebx,10			# image copy
	add	ebx,0x00100000
	mov	dword ptr ds:ram_extadr[RAM_START],ebx
	mov	edx,edi
	mov	ecx,cs:[romsiz]		# get size of compressed rom image
	call	copy_highmem		# copy image into extended memory
	pop	eax
	jc	0b

	mov	cs:[newsiz],eax		# save new extended memory size
	mov	ax,cs
	mov	es,ax
	mov	di,offset oldi15	# set new interrupt
	mov	si,INT_MISC * 4
	mov	bx,offset int15
	call	intset

7:	mov	dx,offset __text_end + 0x07FF
	and	dx,0xF800
	shr	dx,9			# compute new bootrom size
	mov	cs:[romlen],dl		# set new rom length
	mov	cs:pcihdr_romlength[pcihdr],dx
	or	byte ptr ds:ram_flags[RAM_START],RAMFL_CHKSUM

#endif

/*
 * Finally compute the checksum of the RAM data area, the checksum of the
 * bootrom (if necessary) and return to the caller.
 */

8:	mov	si,RAM_START
	mov	ecx,RAM_SIZE		# compute new ram area checksum
	call	dochksum		# (requires 4 stack bytes)
	sub	byte ptr ds:ram_chksum[RAM_START],ah

	xor	ax,ax
	mov	ch,ds:ram_flags[RAM_START]
	test	ch,RAMFL_BBS
	jz	9f			# check if this device is an IPL
	mov	ax,0x0020		# device according to BBS

9:	test	ch,RAMFL_CHKSUM
	jz	0f			# check if the bootrom contents has
	push	ax			# been changed
	mov	cx,cs
	mov	ds,cx
	movzx32	ecx,byte ptr [romlen]
	shl	ecx,9
	xor	si,si
	call	dochksum		# compute checksum for the bootrom
	sub	[newcks],ah
	pop	ax

0:	pop	es
	pop	ds
	call	rststk			# restore the stack and registers
	lret



#ifndef NODDIM
.line __LINE__
/*
 **************************************************************************
 *
 * New 0x15 interrupt. It has been redirected to report the new reduced
 * size of extended memory when using DDIM, but not PMM.
 */
int15:	pushf
	cmp	ah,0x88			# check if we have to report the
	je	4f			# amount of free extended memory
	cmp	ax,0xE801
	je	2f
	cmp	ax,0xE820
	je	1f
	cmp	ax,0xE881
	je	5f
	popf
	ljmp	cs:[oldi15]		# jump to old interrupt handler

/*
 * Functions E820 and E881 are not supported
 */

1:	popf
	mov	ah,0x86			# return with error: function not
	stc				# supported
	jmp	9f

/*
 * Function E801 can return the amount of memory above 16 MB
 */

2:	mov	ax,cs:[newsiz + 0]
	xor	bx,bx			# check if less than 15 MB
	cmp	dword ptr cs:[newsiz],15 * 1024
	jbe	3f
	mov	bx,cs:[newsiz + 0]
	mov	ax,cs:[newsiz + 2]
	sub	bx,15 * 1024		# compute amount of memory above 16 MB
	sbb	ax,0
	shrd	bx,ax,6			# compute number of 64 kB blocks
	mov	ax,15 * 1024		# set amount of memory below 16 MB
3:	mov	cx,ax
	mov	dx,bx
	jmp	8f

/*
 * Function E881 can also return the amount of memory above 16 MB
 */

5:	mov	eax,cs:[newsiz]
	xor	ebx,ebx
	cmp	eax,15 * 1024		# check if less than 15 MB
	jbe	6f
	mov	ebx,15 * 1024
	sub	eax,ebx			# compute amount of memory above 16 MB
	shr	eax,6			# compute number of 64 kB blocks
	xchg	eax,ebx
6:	mov	ecx,eax
	mov	edx,ebx
	jmp	8f

/*
 * Function 88 returns the amount below 16 MB
 */

4:	mov	ax,15 * 1024		# return a maximum of 15 MB
	cmp	dword ptr cs:[newsiz],15 * 1024
	jae	8f
	mov	ax,cs:[newsiz + 0]	# return new memory size
8:	popf
	clc

/*
 * Return to caller with carry flag set properly
 */

9:	push	bp
	mov	bp,sp			# put carry flag onto the stack
	rcr	word ptr [bp + 6],1
	rol	word ptr [bp + 6],1
	pop	bp
	iret
#endif



.line __LINE__
/*
 **************************************************************************
 *
 * Handle a redirected bootstrap interrupt vector.
 */
int18:	cli
	xor	ax,ax
	mov	ss,ax			# set new stack
	mov	sp,BOOTLOAD
	sti

	cmp	word ptr cs:[botvec],INT_BOOTSTRAP * 4
	jne	1f

	xor	dx,dx
	int	INT_DISK		# reset the disk system
	mov	cx,5			# loop counter
	mov	bx,BOOTLOAD		# put loading address into ES:BX
	mov	es,dx
3:	push	cx
	mov	ax,0x0201
	mov	cx,0x0001		# try to load first sector of first
	int	INT_DISK		# track on floppy drive A
	pop	cx
	jnc	4f			# if error, we dont have a floppy
	loop	3b			# try up to 5 times
	jmp	1f

4:	cld
	mov	di,bx
	mov	al,byte ptr es:[di]	# check that the boot sector doesnt
	mov	cx,0x0080		# contain only one value
	repe	scasb
	jcxz	1f			# boot sector is empty so try network

	call	checkram			# check if data area valid
	jc	6f
	mov	eax,dword ptr ds:ram_oldint[RAM_START]
	mov	dword ptr ds:[INT_BOOTSTRAP * 4],eax
6:	mov	word ptr ds:ram_id[RAM_START],0	# make data area invalid
#ifndef NODDIM
	mov	ax,cs
	shl	eax,16
	mov	ax,offset int15			# check if the interrupt 15h
	cmp	dword ptr ds:[INT_MISC * 4],eax	# vector has been changed
	jne	5f
	mov	eax,cs:[oldi15]
	mov	dword ptr ds:[INT_MISC * 4],eax	# restore it
#endif
5:	xor	di,di
	ljmp	0,BOOTLOAD			# call floppy boot sector

1:	push	cs
	call	dorom				# start IPL process
	or	ax,ax				# check error code
	jnz	2f


/*
 * If the bootrom failed, try to start the system using the BIOS. This method
 * should only be used if we dont have BBS.
 */

bootfail:

	mov	si,offset keymsg
	call	prnstr			# wait for key press
	xor	ax,ax
	int	INT_KBD
2:	mov	ax,BIOS_SEG		# try to start the system using the BIOS
	mov	ds,ax
	mov	word ptr ds:[BIOS_RSTFLAG],0
	int	INT_BOOTSTRAP
	jmp	2b



.line __LINE__
/*
 **************************************************************************
 *
 * Start the bootrom. This is called UNDI IPL in the PXE specification.
 * While the code above is just for the BIOS interface, now decompress
 * the bootrom and start the game.
 *
 * Input:  none
 * Output: if network boot successful, routine does not return
 *         if network boot unsusccessful:
 *		if stack cannot get restored, routine does not return
 *		otherwise:
 *			ax = 0  -->  loader error
 *			ax = 1  -->  user abort
 */
dorom:

/*
 * First we have to switch the stack, because we need quite some amount
 * of stack space. Unfortunately, some BIOS incorrectly set the top of
 * stack to 0:0400 instead of 0:7C00, and we would delete the interrupt
 * vector table in that case.
 */

	call	setstk			# set new stack
	push	ds			# save all segment registers
	push	es
	push	fs
	push	gs

/*
 * Print the startup message
 */

	cld
	mov	si,offset prdnam
	call	prnstr			# print bootrom startup message
	mov	si,offset copyrt
	call	prnstr
	call	prcrlf

/*
 * Check that the data area has not been tampered, and mark the data area
 * as invalid so that we can restart the bootrom through a warm boot if
 * necessary.
 * Then restore all interrupts which have been redirected previously. The
 * data area, which resides in the interrupt vector table, doesnt have to
 * be restored. If we cant restore the 0x15 interrupt vector its not such a
 * problem because this code will remain in ROM and the vector will therefore
 * remain valid. But we cant reclaim the used memory in that case.
 */

	call	checkram			# check data area
	setc	bl				# remember status for later
	mov	word ptr ds:ram_id[RAM_START],0	# make data area invalid

	mov	si,cs:[botvec]
	mov	ecx,dword ptr ds:ram_oldint[RAM_START]
	or	bl,bl				# restore with either the orig
	jz	1f				# or default vector (if the ram
	cmp	si,INT_BOOTSTRAP * 4		# area has been destroyed)
	jne	2f
	mov	ecx,BIOS_BOOT_SEG * 65536 + BIOS_BOOT_OFS
1:	jecxz	2f
	mov	dword ptr [si],ecx		# restore it

2:	mov	dx,cs				# save CS into DX
#ifndef NODDIM
	mov	ax,dx
	shl	eax,16
	mov	ax,offset int15			# check if the interrupt
	cmp	dword ptr ds:[INT_MISC * 4],eax	# vector has been changed
	jne	3f
	mov	ecx,cs:[oldi15]
	mov	dword ptr ds:[INT_MISC * 4],ecx	# restore it
#endif

3:	mov	si,offset datmsg		# print error message if ram
	or	bl,bl				# area destroyed
	jnz	error

/*
 * Check if the user really wants to start the netboot process. If not we
 * can simply return to the BIOS. Note that we should not change DX here.
 * Also note that we need to store the timeout value onto the stack since
 * we are still running within a ROM (even when using DDIM the BIOS has
 * turned us read-only by now). When we got started using BBS, the BIOS
 * already cared for selecting the boot device, so there is no need to
 * ask the user again.
 */

	test	byte ptr ds:ram_flags[RAM_START],RAMFL_BBS
	jnz	4f
	mov	ax,cs:[botflg]
	test	ax,ROMFLG_ASKNET
	jz	4f

	push	ebx			# make space on stack for timeout value
	mov	bp,sp			# BP points to timeout value
	and	ax,ROMFLG_ASKTIME
	jz	7f			# check if no timeout
#if ROMFLG_SHIFT_ASKTIME > 0
	shr	ax,ROMFLG_SHIFT_ASKTIME
#endif
	call	settimeout		# set keyboard timeout
	mov	si,offset askmsg	# print message
	call	prnstr
6:	mov	ah,0x01			# check if keystroke available
	int	INT_KBD
	jnz	7f
	call	chktimeout		# check if timeout reached
	jc	6b
	mov	al,0x4E			# assume 'N' if timeout reached
	jmp	8f

7:	xor	ah,ah			# get keystroke
	int	INT_KBD
8:	pop	ebx			# restore stack
	push	ax
	call	prnchr			# print entered character
	call	prcrlf
	pop	ax
	cmp	al,0x59			# check for 'Y'
	je	4f
	cmp	al,0x79			# check for 'y'
	je	4f
	cmp	al,0x5A			# check for 'Z' (e.g. 'Y' on German kbd)
	je	4f
	cmp	al,0x7A			# check for 'z'
	je	4f
	mov	ax,1			# set return error code
	jmp	romend			# return to BIOS

/*
 * Copy this code into RAM so that we can use variables with the decompression
 * code. Also we save the ROM address.
 */

4:	push	ds
	mov	bx,LOADADR
	mov	cx,offset __text_end + 0x000F
	and	cx,0xFFF0
	shr	cx,2
	mov	ds,dx
	mov	es,bx			# move boot loader into lower ram
	xor	si,si
	xor	di,di
	rep	movsd

	cmp	dx,MEMEND		# check if we are running within a
	jb	5f			# physical ROM
	mov	es:[romseg],dx		# save physical ROM segment
5:	push	es
	push	offset 1f		# jump to new copy
	lret


/*
 * We are now in RAM. If a Flash EPROM loader is installed, copy it into
 * RAM as well.
 */

1:	test	word ptr cs:[botflg],ROMFLG_FLOADER
	jz	2f			# check if flash loader installed
	mov	ds,dx
	mov	cx,[si + FLOADSIZEOFS]
	shr	cx,1			# copy flash loader from ROM into
	rep	movsw			# RAM at the end of this bootrom
	pop	ds			# loader
	jmp	4f			# skip DDIM checking

2:	pop	ds

/*
 * Copy the compressed image from extended memory and save the values necessary
 * to access the compressed image.
 */

#ifndef NODDIM
	test	byte ptr ds:ram_flags[RAM_START],RAMFL_DDIM
	jz	3f
	mov	edx,dword ptr ds:ram_extadr[RAM_START]
	mov	ebx,COMPADR * 16
	mov	ecx,cs:[romsiz]		# copy the compressed bootrom image
	call	copy_highmem		# out of extended memory
	mov	si,offset cpymsg
	jc	error
	xor	si,si			# let DX:SI point to new compressed
	mov	dx,COMPADR		# image copy
#endif

3:	mov	word ptr cs:[inptr + 0],si
	mov	word ptr cs:[inptr + 2],dx
	mov	ax,cs:[infadr]		# set pointer for decompressed bootrom
	shl	eax,16			# image
	mov	dword ptr cs:[outptr],eax

/*
 * Check that we have a PnP BIOS and find the PnP BIOS entry point structure.
 * If we have been called according to the BBS, we already know the address
 * of the PnP BIOS entry structure. Otherwise, we have to scan the BIOS memory
 * area.
 * If we dont have a PnP read port number already, call the PnP BIOS to read
 * the configuration information which contains the read port number.
 */

4:	test	byte ptr ds:ram_flags[RAM_START],RAMFL_BBS
	jnz	6f
	mov	word ptr ds:ram_pnpstruct[RAM_START + 0],0
	mov	word ptr ds:ram_pnpstruct[RAM_START + 2],PNPE_STRUCT_SEG
5:	les	di,ds:ram_pnpstruct[RAM_START]
	call	checkpnp
	jnc	6f
	inc	word ptr ds:ram_pnpstruct[RAM_START + 2]
	jnz	5b

6:	cmp	word ptr ds:ram_pnport[RAM_START],0
	jne	7f
	les	di,ds:ram_pnpstruct[RAM_START]
	mov	ax,es
	or	ax,di			# check if we have a PnP BIOS at all
	jz	7f
	sub	sp,PNP_CONF_SIZE	# make space on stack for config struct
	mov	bp,sp
	push	word ptr es:pnpe_real_data[di]
	push	ss
	push	bp			# push far pointer to config struct
	push	PNP_FUNC_GET_CONFIG	# push PnP BIOS opcode
	lcall	es:pnpe_real_entry[di]	# call PnP BIOS
	mov	dx,[bp + PNP_CONF_DATA_PORT]
	add	sp,PNP_CONF_SIZE + 8	# restore stack
	cmp	ax,PNP_RET_SUCCESS	# check for errors
	jne	7f
	mov	ds:ram_pnport[RAM_START],dx

/*
 * If we are running on a PCI device, but havent been called according to
 * the BBS, we have to determine the PCI bus/dev/func number (PFA) in order
 * to pass valid values to the bootrom.
 */

7:	mov	bx,word ptr ds:ram_pcipfa[RAM_START]
	call	checkpci
	mov	si,offset pcimsg	# print error message if device not
	jc	error			# found
	mov	word ptr ds:ram_pcipfa[RAM_START],bx

/*
 * Some network cards offer a flash EPROM for the bootrom. However, various
 * cards dont map the full flash EPROM into the processor space, but just a
 * small window (8kB or 16kB are common) from the start of the flash EPROM.
 * In those cases, the flash programmer can choose to put the compressed
 * bootrom image into the non-visible flash EPROM area. It then adds an
 * access routine at the end of this bootrom loader and puts a flag into
 * the bootrom header. We check this flag, and if set call it with BX = PCI
 * PFA and DX = PnP CSN. It returns an offset to the get-byte routine in AX.
 * If AX is returned zero, the flash EPROM could not get accessed.
 * Otherwise, we can then use the get-byte routine to decompress the bootrom
 * image. If the flash EPROM loader vector is zero, there is no flash EPROM
 * access driver available, and we can simply decompress the bootrom image
 * as it is.
 */

4:	mov	dx,word ptr ds:ram_pnpcsn[RAM_START]
	push	ds
	mov	ax,cs
	mov	ds,ax			# set segment registers
	mov	es,ax

	cli
	mov	di,ss
	mov	si,sp
	mov	ss,ax			# the flash loader and decompression
	mov	sp,0xFFFE		# codes need the stack to be within
	push	di			# the current code segment
	push	si
	sti

	mov	di,offset __text_end + 0x000F
	and	di,0xFFF0
	test	word ptr [botflg],ROMFLG_FLOADER
	jz	2f			# check if flash loader installed
	mov	si,offset romsiz
	call	di			# call flash init routine
	or	ax,ax			# check for error
	mov	si,offset flsmsg	# print error message
	jz	3f
	mov	[getfls],ax		# save offset to get-byte routine

2:	call	inflate			# decompress the block
	or	al,al			# check for error
	mov	si,offset fmtmsg	# print error message
	jnz	3f
	xor	si,si			# no error

3:	cli
	pop	ax
	pop	bx
	mov	ss,bx			# restore stack
	mov	sp,ax
	sti

	pop	ds			# restore data segment
	or	si,si
	jnz	error			# print error message

/*
 * Print the bootrom BIOS options. Dont change DX here!
 */

#ifndef NOPRINT
	mov	si,offset optmsg	# print options message
	call	prnstr
	mov	ch,byte ptr ds:ram_flags[RAM_START]
	test	ch,RAMFL_BBS + RAMFL_DDIM + RAMFL_PMM
	mov	si,offset nonmsg	# print 'none'
	jz	4f

	test	ch,RAMFL_BBS		# check for BBS calling sequence
	jz	2f
	mov	si,offset bbsmsg	# print BBS message
	call	prnstr
	test	ch,RAMFL_DDIM + RAMFL_PMM
	jz	5f
	call	prncom			# print comma

2:	test	ch,RAMFL_DDIM		# check for DDIM option
	jz	5f
	mov	si,offset dimmsg	# print DDIM message
	call	prnstr
	test	ch,RAMFL_PMM		# check for PMM option
	jz	5f
	call	prncom			# print comma
	mov	si,offset pmmmsg	# print PMM message
4:	call	prnstr
5:	call	prcrlf
	call	prcrlf
#endif

/*
 * Prepare the kernel loader structure. We dont pass an address for the
 * UNDI ROMID structure.
 */

	sub	sp,bc_loader_size
	mov	bp,sp
	mov	ax,word ptr ds:ram_pcipfa[RAM_START]
	mov	o_bcl_ax[bp],ax
	mov	ax,word ptr ds:ram_pnpcsn[RAM_START]
	mov	o_bcl_bx[bp],ax
	mov	ax,word ptr ds:ram_pnport[RAM_START]
	mov	o_bcl_dx[bp],ax
	mov	eax,ds:ram_pnpstruct[RAM_START]
	mov	o_bcl_pnp_ptr[bp],eax
	mov	dword ptr o_bcl_undi_romid[bp],0

/*
 * Finally start the bootrom image loader, called base code loader in the
 * PXE specification. But instead of using the loader entry point specified
 * in the ROMID structure, we are using our own entry point. This way we
 * dont need to check for a valid ROMID structure.
 * The loader gets a far pointer to the loader structure as a parameter on
 * the stack.
 *
 * Set the return address so that the bootrom can return to us savely. How-
 * ever, we can only have a return address if this code has NOT been started
 * off a floppy, and it has to return into physical ROM, not our own RAM
 * copy.
 */

	push	ss			# push far pointer to loader struct
	push	bp
	mov	ax,cs:[romseg]
	push	ax			# push ROM segment
	or	ax,ax			# check if we are running off a
	jz	1f			# floppy
	mov	ax,offset bbsret	# use BBS return address
	test	cl,RAMFL_BBS		# check if we are running under BBS
	jnz	1f
	mov	ax,offset nobbs		# use non-BBS return address
1:	push	ax			# push return offset
	mov	ax,cs:[infadr]
	mov	es,ax			# push address of kernel loader
	push	ax
	mov	si,es:[KRNROMIDOFS]
	push	word ptr es:o_kr_loadofs[si]
	lret				# jump to kernel loader


/*
 * If the bootrom returns, we cant simply get back to the BIOS since we
 * dont know if the old BIOS stack is still valid. Therefore, depending on
 * whether we have BBS or not, use interrupts to get back to the BIOS.
 * Note that these routines can only be called within a physical ROM. If
 * we have been started from a floppy, the bootrom is never allowed to
 * return.
 *
 * With BBS, we can use the BOOTFAIL interrupt after a short pause. Other-
 * wise just get back using the BOOTSTRAP interrupt.
 */

bbsret:	call	dowait
	int	INT_BOOTFAIL		# get back to BBS BIOS
nobbs:	jmp	bootfail


/*
 * If we have some sort of error, print an error message and return
 * to the BIOS.
 */

error:	push	si
	mov	si,offset errmsg
	call	prnstr
	pop	si
	call	prnstr			# print error message
	call	prcrlf
	xor	ax,ax			# set return error code
	mov	ds,ax
	test	byte ptr ds:ram_flags[RAM_START],RAMFL_BBS
	jz	romend
	call	dowait

romend:	pop	gs
	pop	fs
	pop	es
	pop	ds			# restore all registers
	call	rststk			# restore stack
	lret				# return to BIOS




.line __LINE__
/*
 **************************************************************************
 *
 * Messages:
 */

errmsg:	.asciz	"Loader error: "
datmsg:	.asciz	"data"
fmtmsg:	.asciz	"format"
cpymsg:	.asciz	"copy"
flsmsg:	.asciz	"flash"
pcimsg:	.asciz	"PCI ID"
keymsg:	.asciz	"Press a key to continue"

#ifndef NOPRINT
optmsg:	.asciz	"BIOS options: "
nonmsg:	.asciz	"none"
bbsmsg:	.asciz	"BBS"
dimmsg:	.asciz	"DDIM"
pmmmsg:	.asciz	"PMM"
#endif

askmsg:	.asciz	"Boot from network (Y/N)? "




.line __LINE__
/*
 **************************************************************************
 *
 * Wait some seconds when using BBS to let the user see an error message
 * Input:  none
 * Output: none
 * Registers changed: BP
 */
dowait:	push	ax
	push	bp
	push	ebx
	mov	al,BBSWAIT
	mov	bp,sp
	call	settimeout
1:	call	chktimeout
	jc	1b
	add	sp,4
	pop	bp
	pop	ax
	ret



.line __LINE__
/*
 **************************************************************************
 *
 * Print a string onto the console screen
 * Input:  CS:SI  -  Pointer to string
 * Output: none
 * Registers changed: AX, SI
 */
prcrlf:	mov	si,offset crlf		# print CR/LF
	jmp	prnstr			# jmp is shorter than "mov al,cs:[si]"

1:	call	prnchr			# send character to screen
	inc	si
prnstr:	mov	al,cs:[si]		# get next character
	or	al,al
	jnz	1b
	ret



.line __LINE__
/*
 **************************************************************************
 *
 * Print a character onto the console screen
 * Input:  AL  -  Character to print
 * Output: none
 * Registers changed: AX
 */
#ifndef NOPRINT
prncom:	mov	al,0x2C			# print a comma
#endif

prnchr:	push	bx
	mov	ah,0x0E			# BIOS command for character output
	mov	bx,0x0007		# screen page and colors
	int	INT_VIDEO		# send character to screen
	pop	bx
	ret



.line __LINE__
/*
 **************************************************************************
 *
 * Save all general registers onto the stack and make sure that we
 * have at least 7 kB of stack space available.
 * Input:  none
 * Output: none
 * Registers changed: SS, SP
 */
setstk:

	push	0
	push	ebp
	mov	bp,sp			# put EDX onto the stack and exchange
	xchg	edx,[bp + 4]		# it with the return address
	shr	edx,16
	push	ecx			# push registers onto the stack
	push	ebx
	push	eax
	mov	ax,ss
	mov	bx,sp
	or	ax,ax			# check if current stack pointer is
	jnz	1f			# below 0000:2000
	cmp	bx,MINSTK
	jae	1f
	cli
	mov	ss,ax			# set new stack right at the beginning
	mov	sp,BOOTLOAD		# of the boot sector load area
	sti
1:	push	esi			# save index registers
	push	edi
	push	ax			# push old stack pointer
	push	bx
	push	dx			# push return address
	push	ds
	mov	ds,ax
	mov	eax,[bx + 0]		# restore all registers from old
	mov	edx,[bx + 16]		# stack
	mov	ecx,[bx + 8]
	mov	ebx,[bx + 4]
	pop	ds
	ret



.line __LINE__
/*
 **************************************************************************
 *
 * Restore all general registers from stack, and restore the stack pointer
 * Input:  none
 * Output: none
 * Registers changed: All registers except EAX
 */
rststk:

	pop	dx			# get return address
	pop	cx			# get old stack pointer
	pop	bx			# get old stack segment
	pop	edi			# get old index registers
	pop	esi
	cli
	mov	ss,bx			# restore old stack
	mov	sp,cx
	sti
	pop	ebx			# get general registers from old stack
	pop	ebx
	pop	ecx
	shl	edx,16
	mov	bp,sp			# exchange EDX value on stack with
	xchg	edx,[bp + 4]		# return address, and restore EDX
	pop	ebp
	add	sp,2
	ret



.line __LINE__
/*
 **************************************************************************
 *
 * Check that we have a correct PCI network device
 * Input:  BX  -  PCI PFA (or 0xFFFF if unknown)
 * Output: BX  -  PCI PFA
 *         Carry flag set if error
 * Registers changed: EAX, EBX
 */
checkpci:

	push	ecx
	push	edx
	push	esi
	push	edi
	mov	ax,cs:pcihdr_vendorid[pcihdr]
	or	ax,cs:pcihdr_deviceid[pcihdr]
	jnz	1f			# check if we have a PCI device
	mov	bx,0xFFFF		# return unknown PFA if not a PCI device
	jmp	9f			# carry will be cleared by 'or'

1:	push	bx
	xor	edi,edi
	mov	ax,PCI_FUNC_INST_CHECK	# check if PCI functions supported
	int	INT_PCI
	pop	bx
	jc	9f
	or	ah,ah
	jnz	8f

	mov	si,cs:[botflg]
	and	si,ROMFLG_DEVNUM	# if request device number is zero,
	jz	3f			# use default search action
#if ROMFLG_SHIFT_DEVNUM > 0
	shr	si,ROMFLG_SHIFT_DEVNUM
#endif
	dec	si			# otherwise search for numbered device
	jmp	2f

3:	cmp	bx,0xFFFF
	je	2f			# check that we dont have a PFA already
	mov	di,PCI_REG_VENDOR_ID
	mov	ax,PCI_FUNC_READ_WORD	# read PCI vendor ID
	int	INT_PCI
	jc	2f			# if error, the known PFA is incorrect
	cmp	cx,cs:pcihdr_vendorid[pcihdr]
	jne	2f			# check for valid vendor ID
	mov	di,PCI_REG_DEVICE_ID
	mov	ax,PCI_FUNC_READ_WORD	# read PCI device ID
	int	INT_PCI
	jc	2f			# if error, the known PFA is incorrect
	cmp	cx,cs:pcihdr_deviceid[pcihdr]
	je	9f			# check for valid device ID

2:	mov	ax,PCI_FUNC_FIND_DEV
	mov	cx,cs:pcihdr_deviceid[pcihdr]
	mov	dx,cs:pcihdr_vendorid[pcihdr]
	int	INT_PCI			# search for specified device
	jc	9f
	or	ah,ah
	jz	9f
8:	stc
9:	pop	edi
	pop	esi
	pop	edx
	pop	ecx
	ret



.line __LINE__
/*
 **************************************************************************
 *
 * Check that the interrupt table contains our data structure
 * Input:  none
 * Output: DS  -  segment of interrupt table
 *         Carry set if data structure not found
 * Registers changed: AX, SI, DS
 */
checkram:

	push	ecx
	xor	ax,ax
	mov	ds,ax
	cmp	word ptr ds:ram_id[RAM_START],RAM_SIGNATURE
	jne	8f
	mov	si,RAM_START
	mov	ecx,RAM_SIZE		# the data signature has to be valid
	call	dochksum		# and the checksum has to be correct
	or	ah,ah			# this will clear the carry bit
	jz	9f
8:	stc				# return with error
9:	pop	ecx
	ret



.line __LINE__
/*
 **************************************************************************
 *
 * Check for $PnP installation structure
 * Input:  ES:DI  -  pointer to installation structure
 * Output: Carry flag set if pointer does not show to a PnP structure
 * Registers changed: AX
 */
checkpnp:

	push	ecx
	mov	cx,es			# check if we have a pointer to a
	or	cx,di			# system BIOS PnP installation check
	jz	8f			# structure

	cmp	dword ptr es:pnpe_sig[di],PNPE_SIGNATURE
	jne	8f

	push	ds
	push	si
	mov	cx,es			# compute checksum of PnP installation
	mov	ds,cx			# check structure
	mov	si,di
	movzx32	ecx,byte ptr pnpe_length[si]
	call	dochksum
	pop	si
	pop	ds
	or	ah,ah			# if its ok, then we have a PnP BIOS
	jz	9f
8:	stc
9:	pop	ecx
	ret



.line __LINE__
/*
 **************************************************************************
 *
 * Compute checksum of a given memory area
 * Input:  DS:SI -  pointer to memory area
 *         ECX   -  size of memory area
 * Output: AH    -  checksum
 * Registers changed: AX, ECX, SI
 */
dochksum:

	cld
	push	ds
	push	dx
	xor	ah,ah
	jecxz	9f			# check if anything to do at all

1:	cmp	si,0x8000
	jb	2f
	mov	dx,ds
	add	dx,0x0800		# dont let the SI register overrun
	mov	ds,dx
	sub	si,0x8000
2:	lodsb
	add	ah,al			# add all bytes together
 addr32	loop	1b

9:	pop	dx
	pop	ds
	ret



#ifndef NODDIM
.line __LINE__
/*
 **************************************************************************
 *
 * Copy into and out of extended memory.
 * Input:  EDX  -  source linear address
 *         EBX  -  destination linear address
 *         ECX  -  number of bytes to copy
 * Output: Carry flag set if error
 * Registers changed: EAX, EBX, ECX, EDX
 */
copy_highmem:

	push	bp
	push	es
	push	si

	inc	ecx
	shr	ecx,1			# convert byte count into word count
1:	push	ecx
	cmp	ecx,0x00008000		# copy a maximum of 0x8000 words at one
	jbe	2f			# time
	mov	cx,0x8000
2:	mov	si,GDTSTRUCTLEN		# get space for GDT on stack
	sub	sp,si
	mov	bp,sp
3:	dec	si
	mov	byte ptr [bp + si],0	# fill GDT with zeroes
	jnz	3b

/*
 * Setup source segment address in GDT
 */

	mov	word ptr [bp + GDTSRCADR + 0],dx
	ror	edx,16
	mov	byte ptr [bp + GDTSRCADR + 2],dl
	mov	byte ptr [bp + GDTSRCHIADR],dh
	ror	edx,16
	mov	word ptr [bp + GDTSRCLEN],0xFFFF
	mov	byte ptr [bp + GDTSRCRIGHT],GDTACCESS

/*
 * Setup destination segment address in GDT
 */

	mov	word ptr [bp + GDTDESTADR + 0],bx
	ror	ebx,16
	mov	byte ptr [bp + GDTDESTADR + 2],bl
	mov	byte ptr [bp + GDTDESTHIADR],bh
	ror	ebx,16
	mov	word ptr [bp + GDTDESTLEN],0xFFFF
	mov	byte ptr [bp + GDTDESTRIGHT],GDTACCESS

/*
 * Actually copy the memory using the BIOS
 */

	mov	ax,ss			# let ES:SI point to the GDT
	mov	es,ax			# CX is already set with the number
	mov	si,bp			# of words to copy
	mov	ah,0x87
	clc
	int	INT_MISC		# call the BIOS to do the copy
	lahf
	add	sp,GDTSTRUCTLEN		# restore stack
	shl	ecx,1
	add	ebx,ecx			# set address to next block
	add	edx,ecx
	pop	ecx			# restore copy count
	sahf
	jc	9f			# check for error
	sub	ecx,0x00008000
	ja	1b			# continue with next block to copy
	clc				# return without error
9:	pop	si
	pop	es
	pop	bp
	ret
#endif



#ifndef NODDIM
.line __LINE__
/*
 **************************************************************************
 *
 * Find POST Memory Manager entry point
 * Input:  none
 * Output: EAX  -  PMM entry point, zero if not found
 * Registers changed: EAX, CX, SI
 */
findpmm:

	push	ds
	mov	ax,PMM_STARTSEG - 1	# start scanning for PMM structure
1:	inc	ax
	jz	8f			# check if at end of memory
	mov	ds,ax
	cmp	dword ptr ds:[pmm_signature],PMM_SIGNATURE
	jne	2f
	xor	si,si
	movzx32	ecx,byte ptr ds:[pmm_length]
	call	dochksum		# check if checksum is correct
	or	ah,ah
	jnz	2f
	mov	eax,dword ptr ds:[pmm_entry]
	or	eax,eax
	jnz	9f			# the entry point should not be zero
2:	mov	ax,ds
	jmp	1b			# continue with next segment

8:	xor	eax,eax			# PMM entry not found
9:	pop	ds
	ret

#endif



#ifndef NODDIM
.line __LINE__
/*
 **************************************************************************
 *
 * Determine amount of extended memory
 * Input:  none
 * Output: EAX  -  amount of extended memory in kB
 * Registers changed: EAX
 */
getext:

	push	ebx
	push	ecx
	push	edx
	mov	ax,0xE801		# try 16-bit version
	int	INT_MISC
	jc	3f
	mov	cx,ax			# some BIOS return incorrect
	or	cx,bx			# values
	jnz	1f
	mov	ax,cx
	mov	bx,dx
1:	cwde				# clear upper word
	and	ebx,0xFFFF
	shl	ebx,6			# multiply by 64
	add	eax,ebx			# and add to amount below 16M
	jmp	7f

3:	mov	ah,0x88			# try very old method
	clc
	int	INT_MISC		# call BIOS to get amount of
	jnc	4f			# extended memory in kB
	xor	ax,ax			# no extended memory found
4:	cwde
7:	pop	edx
	pop	ecx
	pop	ebx
	ret

#endif



.line __LINE__
/*
 **************************************************************************
 *
 * Set timer timeout in 1 second intervalls
 * Input:  AL  -  Timeout value
 *         BP  -  Pointer to 4-byte save space
 * Output: none
 * Registers changed: AX
 */
settimeout:

	push	cx
	push	dx
	mov	ah,18			# compute number of timer ticks
	mul	ah
	push	ax
	xor	ah,ah
	int	INT_TIME
	pop	ax
	add	dx,ax
	adc	cx,0
	mov	word ptr [bp + 0],dx
	mov	word ptr [bp + 2],cx
	pop	dx
	pop	cx
	ret



.line __LINE__
/*
 **************************************************************************
 *
 * Check if timeout value reached
 * Input:  BP  -  Pointer to 4-byte save space
 * Output: Carry flag set if timeout not reached
 * Registers changed: AX
 */
chktimeout:

	push	cx
	push	dx
	xor	ah,ah
	int	INT_TIME
	cmp	cx,word ptr [bp + 2]
	jne	9f
	cmp	dx,word ptr [bp + 0]
9:	pop	dx
	pop	cx
	ret



.line __LINE__
/*
 **************************************************************************
 *
 * Set new interrupt. Dont delete the old vector if it has been saved
 * already.
 * Input:  DS:SI  -  pointer to interrupt vector
 *         CS:BX  -  pointer to new handler routine
 *         ES:DI  -  pointer to old vector repository
 * Output: none
 * Registers changed: EAX
 */
intset:

	pushf
	cli
	mov	ax,cs
	shl	eax,16
	mov	ax,bx
	cmp	dword ptr [si],eax
	je	9f
	xchg	eax,dword ptr [si]
	mov	dword ptr es:[di],eax
9:	popf
	ret



.line __LINE__
/*
 **************************************************************************
 *
 * Return the next byte from the compressed image.
 * Input:  none
 * Output: AL  -  next byte
 *         Zero flag set if end of file, AL is zero in that case
 * Changed registers: AX
 */
getbyte:
	jmp	word ptr [getfls]		# call get-byte routine


readbyte:
	cmp	dword ptr [romsiz],0		# check if at end of file
	jz	9f
	pushf					# remember zero flag
	push	si
	push	ds
	lds	si,[inptr]
	lodsb					# get next byte from compressed
	pop	ds				# image
	test	si,0x000F
	jnz	1f
	shr	si,4				# if offset is on paragraph
	add	word ptr [inptr + 2],si		# boundary, increase segment
	xor	si,si				# and reset offset
1:	mov	word ptr [inptr + 0],si
	dec	dword ptr [romsiz]
	pop	si
	popf					# restore zero flag
9:	ret



.line __LINE__
/*
 **************************************************************************
 *
 * Write a byte to the decompressed image.
 * Input:  AL  -  output byte
 * Output: none
 * Registers changed: none
 */
putbyte:

	push	di
	push	es
	les	di,[outptr]
	stosb				# write byte into output buffer area
	test	di,0x000F
	jnz	1f
	shr	di,4			# adjust segment
	add	word ptr [outptr + 2],di
	xor	di,di
1:	mov	word ptr [outptr + 0],di
	pop	es
	pop	di
	ret




.line __LINE__
/*
 **************************************************************************
 *
 * Define the data segment (we dont have a seperate data segment)
 */

outptr:		.word	0		# ptr into decompressed memory
		.word	DECOMPMEM
inptr:		.long	0		# ptr into compressed memory
romseg:		.word	0		# physical ROM segment
getfls:		.word	readbyte	# get-byte routine




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

	.end

