/*
 **************************************************************************
 *
 * Network driver interface for netboot bootrom
 *
 * Module:  netdrv.S86
 * Purpose: Setup the data area and initialize the network driver interface,
 *          also contains the PXE API entry points
 * Entries: Called by bootrom kernel for initialization
 *
 **************************************************************************
 *
 * Copyright (C) 1998-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: netdrv.S86,v 1.13 2007/01/06 18:30:48 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/undi.i86>

.file __FILE__
.line __LINE__



/*
 **************************************************************************
 *
 * Define the network driver stack and heap size
 *
 * The stack is only required for protected mode API calls
 *
 */

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


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




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

	.global	pxestruct
	.global	bustyp
	.global	drvid
	.global	drvflg

	.extern	drv_load
	.extern	drv_entry


	jmp	old_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
totsiz:	.word	0			# total size in paragraphs


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

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

/*
 * The bus type is put here by the binary patch program. It is specified in
 * the old WfM 1.1 specification.
 */

bustyp:	.byte	PXENV_BUS_ISA		# use ISA bus as default


/*
 * The driver ID is either an EISA device ID, or a PCI device/vendor ID,
 * and is filled in by the binary patch program.
 */

drvid:	.long	0			# driver ID


/*
 * The following flags get set by the binary patch program, and are specific
 * to the individual driver interface (e.g. the packet driver interface can
 * interpret the flags differently than the NDIS interface)
 */

drvflg:	.word	0			# driver flags


/*
 * Network driver 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	UNDI_ROMID_SIG		# structure ID
	.byte	UNDI_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	netstart		# offset to driver loader
	.word	__stack_size		# stack size required for network driver
	.word	__bss_end		# required data segment size
	.word	__text_end		# required code segment size
	.ascii	"ISAR"			# bus type for this device


/*
 * PXENV structure as defined in Intels PXE specification. The descriptor
 * table in the !PXE structure doesnt get initialized here. This way, the
 * assembler will set it to all zeroes, and the compression routine in the
 * binary patch program will be able to compress it to a minimum size lateron.
 */

	.align	16
pxenv:	.ascii	"PXENV+"		# signature
	.word	0x0201			# API version 2.1
	.byte	pxentry_size		# size of this structure
	.byte	0			# checksum
	.long	old_pxentry		# far ptr to real-mode old entry point
	.long	0			# far ptr to protected-mode entry point
	.word	0			# kernel stack segment
	.word	0			# kernel stack size
	.word	0			# kernel code segment
	.word	0			# kernel code size
	.word	0			# kernel data segment
	.word	0			# kernel data size
	.word	0			# network driver data segment
	.word	0			# network driver data size
	.word	0			# network driver code segment
	.word	0			# network driver code size
	.long	pxestruct		# far ptr to !PXE structure

	.align	16
pxestruct:
	.ascii	"!PXE"			# !PXE structure signature
	.byte	pxe_def_size		# size of this structure
	.byte	0			# checksum
	.byte	0			# revision of this structure is zero
	.byte	0			# reserved
	.long	romid			# far ptr to UNDI ROM ID structure
	.long	0			# far ptr to kernel ROM ID structure
	.long	new_pxentry16		# far ptr to real-mode new entry point
	.long	new_pxentry32		# far ptr to 32-bit entry point
	.long	0			# far ptr to DHCP/TFTP callout procedure
	.byte	0			# reserved
	.byte	PXE_DESC_DEF_NUM	# number of segment descriptors
	.word	0			# first protected mode selector
	.space	desc_size		# stack segment
	.space	desc_size		# UNDI data segment
	.space	desc_size		# UNDI code segment
	.space	desc_size		# UNDI code write segment
	.space	desc_size		# kernel data segment
	.space	desc_size		# kernel code segment
	.space	desc_size		# kernel code write segment
	.space	desc_size * 2		# spare descriptors


/*
 * The following variables have to be accessible by CS. Since the bootrom code
 * will only run after being loaded into RAM, there is no problem writing into
 * any of these variables.
 */

oldint:	.long	0			# old PXE interrupt vector
realds:	.word	0			# real mode data segment



/*
 **************************************************************************
 *
 * Start the network driver. This will load all network driver parts into
 * memory reserved by the kernel loader and setup the PXENV and !PXE structures.
 * Input:  1. Arg -  far pointer to loader structure
 * Output: ES:DI  -  pointer to PXENV structure
 *         AX     -  error code
 * Registers changed: EAX, EBX, ECX, EDX, SI, DI
 *
 */
netstart:

	cld
	push	bp
	mov	bp,sp
	push	ds			# save all registers
	push	es
	push	fs
	call	1f			# adjust IP to start at 0
1:	pop	si
	sub	si,offset 1b

/*
 * Copy the network driver interface code into its final place. Then compute
 * the new address of the data and BSS segment and call the new copy.
 */

	push	cs
	pop	ds
	lfs	bx,[bp + 6]		# get pointer to loader structure
	mov	ax,fs:o_ul_undi_cs[bx]	# determine UNDI text segment
	mov	es,ax
	xor	di,di
	mov	cx,offset __text_end + 0x000F
	and	cx,0xFFF0
	shr	cx,1			# copy code segment
	rep	movsw

	mov	dx,fs:o_ul_undi_ds[bx]	# determine UNDI data segment
	mov	es,dx
	xor	di,di
	mov	cx,offset __data_end + 1
	shr	cx,1			# copy data segment
	rep	movsw

	push	ax
	push	offset 4f		# push address of new copy
	lret				# call new copy

/*
 * Setup some entries in the PXENV+ and !PXE structure early enough so that they
 * can be used by the network driver loader.
 */

4:	push	ds
	pop	es
	mov	ds,ax
	mov	cs:[realds],dx
	mov	o_pe_rm_entry[pxenv + 2],ax
	mov	o_pe_pxe_ptr[pxenv + 2],ax
	mov	o_pe_net_cs_sel[pxenv],ax
	mov	o_pe_net_ds_sel[pxenv],dx

	mov	o_ps_undiromid[pxestruct + 2],ax
	mov	o_ps_pxentry16[pxestruct + 2],ax
	mov	o_ps_pxentry32[pxestruct + 2],ax
	mov	o_ps_desc[pxestruct + (PXE_DESC_UNDI_DATA * desc_size) + o_desc_segment],dx
	mov	o_ps_desc[pxestruct + (PXE_DESC_UNDI_CODE * desc_size) + o_desc_segment],ax
	mov	o_ps_desc[pxestruct + (PXE_DESC_UNDI_WRITE * desc_size) + o_desc_segment],ax

/*
 * Now DS:SI contains the source address of the network driver proper. Compute
 * the first usable segment following our own BSS segment, switch to our data
 * segment and call the network driver loader. It gets the following parameters:
 *
 *  AX     -  loader text segment
 *  CX     -  first segment following loader text segment
 *  DX     -  first usable segment for network driver
 *  ES:SI  -  pointer to network driver source
 *  FS:BX  -  pointer to UNDI loader structure
 */

	mov	ds,dx			# set new data segment
	mov	cx,offset __bss_end + 0x000F
	shr	cx,4			# compute first free segment
	add	dx,cx
	mov	cx,offset __text_end + 0x000F
	shr	cx,4			# compute first segment following
	add	cx,ax			# our own text segment
	call	drv_load		# call network driver loader
	or	ax,ax			# check for error
	jnz	8f

/*
 * We can now set the PXENV and !PXE structure. For this we switch DS back
 * to the code segment. We dont need to compute a checksum here, as that will
 * be done by the kernel loader after it has inserted its own values.
 * The descriptor table will be preset so that the kernel loader only has
 * to fill in the missing values and set the correct checksum.
 */

	mov	bx,ds
	mov	ax,cs
	mov	ds,ax

	mov	cx,ax
	sub	cx,bx			# compute real size of data segment
	jnc	5f
	neg	cx
5:	test	cx,0xF000		# check if data segment is too large
	jz	6f
	mov	ax,PXENV_STATUS_LOADER_UNDI_IMG
	jmp	8f

6:	shl	cx,4			# convert data size into byte number
	mov	dx,o_ur_codesize[romid]	# determine code size
	movzx32	ebx,bx			# convert data segment base into linear
	shl	ebx,4			# address
	movzx32	eax,ax			# convert code segment base into linear
	shl	eax,4			# address

	mov	o_pe_net_cs_size[pxenv],dx
	mov	o_pe_net_ds_size[pxenv],cx
	mov	o_ps_desc[pxestruct + (PXE_DESC_UNDI_DATA * desc_size) + o_desc_addr],ebx
	mov	o_ps_desc[pxestruct + (PXE_DESC_UNDI_DATA * desc_size) + o_desc_size],cx
	mov	o_ps_desc[pxestruct + (PXE_DESC_UNDI_CODE * desc_size) + o_desc_addr],eax
	mov	o_ps_desc[pxestruct + (PXE_DESC_UNDI_CODE * desc_size) + o_desc_size],dx
	mov	o_ps_desc[pxestruct + (PXE_DESC_UNDI_WRITE * desc_size) + o_desc_addr],eax
	mov	o_ps_desc[pxestruct + (PXE_DESC_UNDI_WRITE * desc_size) + o_desc_size],dx
	xor	ax,ax

/*
 * Thats it, we can now return to the kernel loader. The PXE interrupt 0x1A will
 * get redirected lateron with the UNDI_START_UNDI call. But first we prepare
 * the UNDI loader structure properly with some return values.
 */

8:	les	bx,[bp + 6]		# get pointer to loader structure
	mov	word ptr es:o_ul_status[bx],ax
	mov	word ptr es:o_ul_pxeptr[bx + 0],offset pxestruct
	mov	word ptr es:o_ul_pxeptr[bx + 2],cs
	mov	word ptr es:o_ul_pxenvptr[bx + 0],offset pxenv
	mov	word ptr es:o_ul_pxenvptr[bx + 2],cs
	or	ax,ax
	jz	9f			# return with error
	mov	ax,PXENV_EXIT_FAILURE
9:	pop	fs
	pop	es			# restore registers
	pop	ds
	pop	bp
	lret



/*
 **************************************************************************
 *
 * New-style PXE API entry point for 32-bit stack segment
 * Input:  1. Arg  -  function code
 *         2. Arg  -  far pointer to PXE function structure
 * Output: Error code
 *         Carry flag set if error
 * Registers changed: AX
 *
 */
new_pxentry32:

	push	ds
	push	es
	pushad
	mov	ebp,esp
 addr32	mov	bx,[ebp + PUSHAD_SIZE + 12]
 addr32	les	di,[ebp + PUSHAD_SIZE + 14]

	cmp	bx,UNDI_MINNUM
	jb	1f
	cmp	bx,UNDI_MAXNUM		# dont accept base code calls
	jbe	2f			# in 32-bit protected mode
1:	mov	word ptr es:[di],PXENV_STATUS_PMODE
	mov	ax,PXENV_EXIT_FAILURE
	stc
	jmp	8f

2:	mov	cx,ss
	mov	edx,esp
	mov	ax,cs:o_ur_stacksize[romid]
	or	ax,ax			# check if we need a stack at all
	jz	4f
	mov	ax,cs:o_desc_segment[pxestruct + pxe_stack_sel]
	or	ax,ax
	jz	1b			# check if we have a local stack
	cmp	ax,cx			# check if we are using the local
	je	4f			# stack already

	pushf
	pop	si			# save interrupt flag
	cli
	mov	ss,ax			# switch to new 16-bit stack
	movzx32	esp,word ptr cs:o_desc_size[pxestruct + pxe_stack_sel]
	push	si
	popf				# restore interrupt flag
4:	push	cx
	push	edx			# save old stack pointer

	call	do_pxentry		# call function handler

	pop	edx
	pop	cx			# restore old stack pointer
	pushf
	pop	si			# save interrupt flag
	cli
	mov	ss,cx			# restore old stack
	mov	esp,edx
	push	si
	popf				# restore interrupt flag

8:	cwde
 addr32	mov	[ebp + PUSHAD_AX],eax	# save return value
	popad
	pop	es
	pop	ds
 data32	lret				# 32-bit return address!!



/*
 **************************************************************************
 *
 * New-style PXE API entry point for 16-bit stack segment
 * Input:  1. Arg  -  function code
 *         2. Arg  -  far pointer to PXE function structure
 * Output: Error code
 *         Carry flag set if error
 * Registers changed: AX
 *
 */
new_pxentry16:

	push	ds
	push	es
	pushad
	mov	bp,sp
	mov	bx,[bp + PUSHAD_SIZE + 8]
	les	di,[bp + PUSHAD_SIZE + 10]
8:	call	do_pxentry
	mov	[bp + PUSHAD_AX],ax
	popad
	pop	es
	pop	ds
	lret



/*
 **************************************************************************
 *
 * Old-style PXE API entry point
 * Input:  BX     -  function code
 *         ES:DI  -  far pointer to PXE function structure
 * Output: AX     -  error code
 *         Carry flag set if error
 * Registers changed: AX
 *
 */
old_pxentry:

	push	ds
	push	es
	pushad
	mov	bp,sp
	jmp	8b



/*
 **************************************************************************
 *
 * Actual PXE API entry point handler
 * Input:  BX     -  function code
 *         ES:DI  -  far pointer to PXE function structure
 * Output: AX     -  error code
 * Registers changed: (E)AX, (E)BX, (E)CX, (E)DX, SI, DS
 *
 */
do_pxentry:

	cmp	bx,UNDI_MINNUM		# check if we are dealing with a
	jb	1f			# UNDI function code
	cmp	bx,UNDI_MAXNUM
	jbe	3f

/*
 * Handle calls to the kernel PXE entry point. If the kernel code has already
 * been removed, we have to return an error code. Note that the application
 * is allowed to call the base code in the time between setting up the seg-
 * ment descriptor in the !PXE structure and switching the processor into
 * protected mode. Interrupts are no factor here, because they don't get
 * routed through this interface (see below for a discussion about the UNDI
 * data segment).
 */

1:	mov	ax,PXENV_STATUS_BAD_FUNC
	mov	dx,word ptr cs:o_desc_segment[pxestruct + pxe_base_data_sel]
	or	dx,dx
	jz	8f
	mov	cx,word ptr cs:o_desc_segment[pxestruct + pxe_base_text_sel]
	or	cx,cx
	jz	8f

	mov	ds,dx			# set kernel data segment
	push	cs			# push pointer to !PXE structure
	push	offset pxestruct
	push	es			# push pointer to parameter structure
	push	di
	push	bx			# push function code
	push	cs			# push return address
	push	offset 2f
	push	cx			# push pointer to kernel entry point
	push	KRNAPIENTRY
	lret				# call kernel PXE API entry point

2:	add	sp,10			# restore stack
	ret				# return to caller


/*
 * Now handle UNDI API calls.
 * We check to see if we are running in protected mode, and set the UNDI
 * data segment. However, this is not that easy. The data segment selector
 + in the !PXE structure usually contains the correct value for the seg-
 * ment register. But when an application switches into protected mode,
 * it will first modify the GDT and the !PXE segment descriptors, and then
 * switch into protected mode some time later. If an interrupt occurs in
 * the mean time, the base code will call UNDI. In this situation, we have
 * protected mode selectors in the !PXE structure, but are still running
 * in real mode. To solve this, in real mode we use our own storage for
 * the data segment register value, which is stored in the code segment.
 */

3:	mov	dx,cs:[realds]		# get real mode data segment
	xor	cl,cl
	mov	eax,cr0
	test	ax,0x0001		# check protected mode flag
	jz	4f
	pushfd
	pop	eax
	bt	eax,17			# check virtual-8086 mode flag
	jc	4f
	inc	cl			# set protected mode flag if not VM86
	mov	dx,word ptr cs:o_desc_segment[pxestruct + pxe_undi_data_sel]
4:	mov	[protflag],cl		# save protected mode flag
	mov	ds,dx			# set data segment register


/*
 * Check for invalid function calls in protected mode
 */
	cmp	bx,UNDI_ISR		# handle ISR call as fast as
	je	7f			# possible
	or	cl,cl			# check for protected mode
	jz	1f
	mov	ax,PXENV_STATUS_PMODE
	cmp	bx,UNDI_START_UNDI
	je	8f
	cmp	bx,UNDI_STOP_UNDI
	je	8f
	cmp	bx,UNDI_STARTUP
	je	8f
	cmp	bx,UNDI_CLEANUP
	je	8f


/*
 * Handle UNDI_START_UNDI call. After letting the driver set all
 * interrupts we have to set interrupt 0x1A for compatibility with
 * the old PXE specification.
 * This function gets called by the base code once at startup. Every
 * call thereafter results in an error. Therefore, this function can
 * never get executed in protected mode.
 */

1:	cmp	bx,UNDI_START_UNDI
	jne	5f
	mov	ax,PXENV_STATUS_UNDI_INVALID_STATE
	cmp	dword ptr cs:[oldint],0	# check if UNDI already initialized
	jne	8f
	call	drv_entry		# let the driver handle the request
	or	ax,ax
	jnz	8f			# dont set interrupt on error
	xor	dx,dx
	mov	ds,dx
	mov	cx,cs
	shl	ecx,16
	mov	cx,offset new_int1A	# set new interrupt vector
	xchg	ecx,ds:[INT_PXE * 4]
	mov	cs:[oldint],ecx		# save old interrupt vector
	jmp	8f

/*
 * Handle UNDI_STOP_UNDI. This will restore the interrupt 0x1A vector
 * before we let the driver handle the call.
 */

5:	cmp	bx,UNDI_STOP_UNDI
	jne	7f
	mov	ax,PXENV_STATUS_UNDI_INVALID_STATE
	cmp	dword ptr cs:[oldint],0	# check if UNDI initialized
	je	8f
	push	ds
	mov	cx,cs
	xor	ax,ax
	mov	ds,ax			# check that the interrupt really is
	shl	ecx,16			# ours
	mov	cx,offset new_int1A
	cmp	ds:[INT_PXE * 4],ecx
	jne	6f
	mov	eax,cs:[oldint]		# restore old interrupt
	mov	ds:[INT_PXE * 4],eax
	pop	ds
7:	call	drv_entry		# call driver PXE entry point
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:	ret

/*
 * If we cant unhook the interrupt 0x1A we have to call the driver anyway
 * but have to return an error code when the driver returns without an
 * error.
 */

6:	pop	ds
	call	drv_entry		# now let the driver handle the request
	or	ax,ax			# the error code of the driver takes
	jnz	8b			# precedence
	mov	ax,PXENV_STATUS_KEEP_UNDI
	jmp	8b



/*
 **************************************************************************
 *
 * PXE interrupt handler. It will just return the address of the PXENV
 * structure.
 * Input:  AX     -  input magic number
 * Output: AX     -  output magic number
 *         ES:BX  -  far pointer to PXENV structure
 *         EDX    -  linear address of the PXENV structure
 *         Carry flag cleared
 * Registers changed: AX
 *
 */
new_int1A:

	cmp	ax,PXE_IN_MAGIC		# check for input magic number
	je	1f
	ljmp	cs:[oldint]		# daisy chain into old interrupt vector

1:	mov	ax,cs
	mov	es,ax			# set ES:BX with the address of
	mov	bx,offset pxenv		# the PXENV structure
	movzx32	edx,ax
	shl	edx,4			# convert the address into a linear
	add	edx,offset pxenv	# address

	push	bp			# clear carry in the flag word on the
	mov	bp,sp			# stack
	and	word ptr [bp + 6],0xFFFE
	pop	bp
	mov	ax,PXE_OUT_MAGIC	# set return magic number
	iret




/*
 **************************************************************************
 *
 * BSS segment
 *
 */

	.global	protflag

	.lcomm	protflag,1		# protected mode flag




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

