/*
 **************************************************************************
 *
 * Network driver interface for netboot bootrom
 *
 * Module:  undi.S86
 * Purpose: Initialize the UNDI driver interface
 * Entries: drv_load, drv_entry
 *
 **************************************************************************
 *
 * 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: undi.S86,v 1.10 2007/01/06 18:30:50 gkminix Exp $
 *
 */


/*
 **************************************************************************
 *
 * Include assembler macros:
 *
 */
#include <common.i86>
#include <romconfig.i86>
#include <system/bootrom.i86>
#include <system/bios.i86>
#include <system/pci.i86>
#include <pxe/general.i86>
#include <pxe/entry.i86>
#include <pxe/romid.i86>

.file __FILE__
.line __LINE__



/*
 **************************************************************************
 *
 * BSS segment:
 *
 */
	.bss

	.lcomm	undi_entry,2		# offset to UNDI entry point




/*
 **************************************************************************
 *
 * Start code segment.
 *
 */
	.text

	.global	drv_load		# define entry points
	.global	drv_entry

	.extern	prnstr



/*
 **************************************************************************
 *
 * Load and initialize the UNDI driver interface. When an error
 * occurred, it will print an error message before returning.
 * Input:  ES:SI  -  pointer to network driver source
 *         FS:BX  -  pointer to UNDI loader structure
 *         AX     -  loader text segment
 *         CX     -  first segment following loader text segment
 *         DX     -  first usable paragraph for network driver
 * Output: AX     -  PXE error code
 * Registers changed: EAX, EBX, ECX, EDX, SI, DI, ES
 *
 */
drv_load:

/*
 * Check that the UNDI driver is correct. An UNDI driver image starts with
 * the following structure:
 *
 *    0000  -  ID string "UNDI"
 *    0004  -  size of text segment in bytes
 *    0006  -  size of data segment in bytes
 *    0008  -  size of BSS segment in bytes
 *    000A  -  offset to driver entry point within text segment
 *    000C  -  offset to start of text segment
 *    000E  -  offset to start of data segment
 *    0010  -  size of stack
 *    0012  -  size of heap
 *
 * The segment sizes include those of the UNDI network driver loader (e.g.
 * the code in this source file). This is due to makerom reading the UNDI
 * driver as an ELF relocatable object file, and adding the text and data
 * offsets to all relocations, which includes the values in the UNDI driver
 * header.
 */

	cld
	mov	ax,cx
	mov	di,si
	mov	si,offset idstr
	mov	cx,4
	repe	cmpsb			# check that the driver starts with
	jne	8f			# the proper signature
	sub	di,4

	mov	cx,cs
	mov	si,es:[di + 0x0004]	# get size of text segment
	add	si,0x000F
	rcr	si,1			# adjust to paragraph boundary
	shr	si,3
	add	cx,si			# compute end of text segment
	cmp	cx,ax			# check if text segment size is
	jbe	8f			# not below size of loader

	mov	cx,cs:[NETROMIDOFS]
	xchg	cx,si			# get total text size of network
	mov	ax,cs:o_ur_codesize[si]	# driver
	add	ax,0x000F
	rcr	ax,1
	shr	ax,3
	cmp	cx,ax			# check that text segment size is
	ja	8f			# not too large

	mov	cx,offset __text_end + 0x000F
	and	cx,0xFFF0
	cmp	cx,es:[di + 0x000C]	# check if text offset is correct
	jne	8f

	mov	cx,ds
	mov	ax,es:[di + 0x0008]	# get size of BSS + data segment
	add	ax,0x000F
	rcr	ax,1			# adjust to paragraph boundary
	shr	ax,3
	add	cx,ax			# compute end of data segment
	mov	ax,es:[di + 0x0010]
	add	ax,es:[di + 0x0012]	# compute size of heap
	jc	8f
	add	ax,0x000F
	rcr	ax,1			# convert into paragraphs
	shr	ax,3
	add	cx,ax			# compute total data segment size
	cmp	cx,dx			# check if data segment size is
	jbe	8f			# not below size of loader

	mov	cx,cs:o_ur_datasize[si]	# get total size of network driver
	add	cx,0x000F		# data segment
	rcr	cx,1
	shr	cx,3
	cmp	ax,cx			# check that data segment size is
	ja	8f			# not too large

	mov	cx,offset __bss_end + 0x000F
	and	cx,0xFFF0
	cmp	cx,es:[di + 0x000E]	# check if data offset is correct
	jne	8f

	mov	ax,es:[di + 0x0006]	# get size of data segment
	cmp	ax,es:[di + 0x0008]	# has to be smaller than BSS size
	jbe	1f

8:	mov	si,offset errmsg
	call	prnstr			# print error message
	mov	ax,PXENV_EXIT_FAILURE
	jmp	9f

/*
 * Save the pointer to the UNDI driver entry point
 */

1:	mov	ax,es:[di + 0x000A]
	cmp	ax,es:[di + 0x000C]	# check if entry point is within
	jb	8b			# text segment
	cmp	ax,es:[di + 0x0004]
	jae	8b
	mov	word ptr [undi_entry],ax

/*
 * Remember the text and data sizes for later
 */

	mov	cx,es:[di + 0x0004]	# get text segment size
	sub	cx,es:[di + 0x000C]
	jbe	8b
	add	cx,0x000F
	and	cx,0xFFF0		# adjust to paragraph boundary

	mov	dx,es:[di + 0x0006]	# get data segment size
	sub	dx,es:[di + 0x000E]
	jbe	8b

/*
 * Copy the driver text and data segments into their final place
 */

	push	ds
	mov	ax,es
	mov	ds,ax
	mov	si,di
	mov	di,[si + 0x000C]	# get text segment offset
	mov	ax,cs
	mov	es,ax
	mov	ax,[si + 0x000E]	# get data segment offset
	shr	cx,1
	rep	movsw			# copy text segment
	mov	di,ax
	pop	ax
	push	ax
	mov	es,ax
	mov	cx,dx
	shr	cx,1
	rep	movsw			# copy data segment
	jnc	2f
	movsb
2:	pop	ds

/*
 * Now call the loaded driver to finish any memory initialization
 * The driver gets the function code 0xFFFF which is not defined in
 * the UNDI sepcification. The parameter points to the UNDI loader
 * structure.
 */

	push	cs			# push far return address
	push	offset 3f
	mov	ax,fs
	mov	es,ax
	mov	di,bx			# load pointer to UNDI loader structure
	mov	bx,0xFFFF		# into ES:DI and function code into BX
	xor	ax,ax
	jmp	word ptr [undi_entry]	# call UNDI driver

3:	or	ax,ax			# check for error
	jnz	8b
9:	ret



/*
 **************************************************************************
 *
 * Jumps to the UNDI driver entry point.
 * Input:  BX     -  UNDI function code
 *         ES:DI  -  pointer to parameter structure
 * Output: AX     -  error code
 * Registers changed: AX, BX, CX, DX, SI
 *
 * This code just jumps to the UNDI driver entry point. It does not
 * process any arguments. This means, that the stack segment doesn't
 * necessarily be the same as the data segment. The called UNDI handler
 * code is not allowed to modify the ES and DI registers.
 */
drv_entry:

	pop	ax
	push	cs			# prepare return address for a far
	push	ax			# return
	jmp	word ptr [undi_entry]	# call UNDI driver




/*
 **************************************************************************
 *
 * Messages and constant data:
 *
 */
	_rodata

idstr:	.ascii	"UNDI"			# UNDI driver ID string


errmsg:	.byte	0x0D,0x0A
	.asciz	"UNDI ERROR: invalid driver"




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

