/*
 **************************************************************************
 *
 * Network driver interface for netboot bootrom
 *
 * Module:  packet.S86
 * Purpose: Packet driver to UNDI interface
 * Entries: 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: packet.S86,v 1.16 2007/01/06 18:30:49 gkminix Exp $
 *
 */


/*
 **************************************************************************
 *
 * Include assembler macros:
 *
 */
#include <common.i86>
#include <romconfig.i86>
#include <system/bootrom.i86>
#include <net/ethernet.i86>
#include <net/in.i86>
#include <pxe/general.i86>
#include <pxe/undi.i86>
#include <pxe/entry.i86>
#include "pktdrv.i86"

.file __FILE__
.line __LINE__



/*
 **************************************************************************
 *
 * Definition of service flags. See NDIS-2.0 specification for further
 * explanation.
 *
 */
#ifdef NEEDMCAST
#define	FLAGS_MCAST	0b0000000000000010
#else
#define	FLAGS_MCAST	0b0000000000000000
#endif

#ifdef NEEDSETADDR
#define	FLAGS_SETADDR	0b0000000000010000
#else
#define	FLAGS_SETADDR	0b0000000000000000
#endif

#define	FLAGS_STANDARD	0b0000110000001001



/*
 **************************************************************************
 *
 * Values for initialization flag:
 *
 */
#define	FLAG_NOTSTART	0			/* UNDI driver not started */
#define	FLAG_NOTINIT	UNDI_STATE_STARTED	/* device not initialized */
#define	FLAG_INIT	UNDI_STATE_INITIALIZED	/* device is initialized */
#define	FLAG_OPEN	UNDI_STATE_OPENED	/* device has been opened */



/*
 **************************************************************************
 *
 * Transmit retry values:
 *
 */
#define	XMT_RETRY	10		/* retry 10 times */
#define	XMT_WAIT	5		/* wait 50 ms between retries */



/*
 **************************************************************************
 *
 * Multicast hardware address (first three bytes):
 *
 */
#define	HW_MCAST_1	0x01		/* first byte of hardware mcast addr */
#define	HW_MCAST_2	0x00		/* second byte of hardware mcast addr */
#define	HW_MCAST_3	0x5E		/* third byte of hardware mcast addr */

#define	MCAST_BITS	64		/* size of multicast buffer in bits */
#define	MCAST_BYTES	(MCAST_BITS/8)	/* size of multicast buffer in bytes */




.line __LINE__
/*
 **************************************************************************
 *
 * BSS segment:
 *
 */
	.bss				# start BSS segment

	.extern	irqnum			# hardware IRQ number
	.extern	pktvect			# packet driver interrupt vector
	.extern	protflag		# protected mode flag
	.extern	devinfo			# device info structure


	.lcomm	hwaddr,ETH_ALEN		# buffer to hold real hardware address
#ifdef NEEDSETADDR
	.lcomm	curaddr,ETH_ALEN	# buffer to hold current hardware addr
#endif
#ifdef NEEDSTAT
	.lcomm	xmtgood,4		# number of successful transmissions
	.lcomm	rcvgood,4		# number of good frames received
	.lcomm	rcvresource,4		# number of frames discarded
#endif
	.lcomm	pkthandle,2		# packet driver handle
	.lcomm	pktinfo,4		# far ptr to packet driver info struct
	.lcomm	readsize,2		# actual size of data in buffer
	.lcomm	readbuf,ETH_BUF_SIZE	# read buffer
	.lcomm	writebuf,ETH_BUF_SIZE	# write buffer
	.lcomm	serviceflags,2		# service flags
	.lcomm	pktfilter,2		# packet filter
	.lcomm	pkttype,2		# type of received packet
	.lcomm	initflag,1		# flag to indicate if initialized
#ifdef NEEDSETADDR
	.lcomm	addrflag,1		# flag set when hardware addr changed
#endif
	.lcomm	isrflag,1		# flag set when inside ISR handler
	.lcomm	intcount,1		# number of interrupts received
#if MASKINT == 1
	.lcomm	intdisable,1		# disable interrupt masking
#endif
	.lcomm	pktfunc,1		# packet driver functionality

#ifdef NEEDMCAST
	.lcomm	mcastbuf,MCAST_BYTES	# multicast address buffer
#endif




/*
 **************************************************************************
 *
 * Data segment:
 *
 */
	.data

/*
 * Default packet driver info structure for drivers which dont support the
 * get_parameters call.
 */

def_info:
	.byte	1			# driver conforms to version 1.09
	.byte	9
	.byte	14			# size of this structure
	.byte	ETH_ALEN		# length of network hardware address
	.word	ETH_FRAME_MAX		# MTU including MAC header
	.word	0			# buffer size for multicast addresses
	.word	0			# no of back-to-back MTU receives - 1
	.word	0			# no of successive transmits - 1
	.word	0			# post-EOI interrupt hook number




/*
 **************************************************************************
 *
 * Constant data:
 *
 */
	_rodata


iface_type:
	.ascii	"DIX+802.3"		# default interface type
iface_type_end:




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

	.global	drv_entry		# define entry points

/*
 * External variables in code space
 */

	.extern	pxestruct
	.extern	drvflg

/*
 * External routines for interrupt handling
 */

	.extern	intstart
	.extern	intstop
	.extern	intset
	.extern	intreset
	.extern	dohwint
	.extern	endhwint

/*
 * External routines in the general network driver interface library
 */

	.extern	wait10ms
	.extern	switchreal
	.extern	switchprot



/*
 **************************************************************************
 *
 * PXE API entry point. Since we get called by the general network driver
 * interface API entry which already checked if the function number is for
 * us, we dont have to do that again here. We can safely assume that the
 * function number is always correct. Also, the general network driver
 * interface API already saved all registers for us.
 * Input:  ES:DI  -  pointer to parameter structure
 *         BX     -  function number
 * Output: AX     -  error code
 * Registers changed: EAX, BX, CX, DX, SI
 *
 */
drv_entry:

	test	byte ptr [protflag],0xFF
	jz	1f			# check protected mode flag
	call	switchreal		# switch to real mode
	jnc	1f
	mov	ax,PXENV_STATUS_PMODE	# not available in protected mode
	ret

1:	shl	bx,1			# compute offset into jump table
	call	cs:jmptab[bx]		# call function handler

	test	byte ptr [protflag],0xFF
	jz	2f
	call	switchprot		# switch back to protected mode
2:	ret



/*
 **************************************************************************
 *
 * Function table
 *
 */
jmptab:	.word	pxe_start_undi		# start UNDI interface
	.word	pxe_startup		# startup driver
	.word	pxe_cleanup		# cleanup driver
	.word	pxe_initialize		# initialize driver
	.word	pxe_reset_nic		# reset network interface
	.word	pxe_shutdown		# shutdown driver
	.word	pxe_open		# open network driver
	.word	pxe_close		# close network driver
	.word	pxe_transmit		# transmit a packet
#ifdef NEEDMCAST
	.word	pxe_set_multicast	# set multicast address
#else
	.word	pxe_unsupported		# set multicast address
#endif
#ifdef NEEDSETADDR
	.word	pxe_set_addr		# set station address
#else
	.word	pxe_unsupported		# set station address
#endif
	.word	pxe_set_filter		# set packet filter
	.word	pxe_get_info		# get network driver information
#ifdef NEEDSTAT
	.word	pxe_get_stat		# get network statistics
	.word	pxe_clear_stat		# clear statistics
#else
	.word	pxe_unsupported		# get network statistics
	.word	pxe_unsupported		# clear statistics
#endif
	.word	pxe_unsupported		# initiate diagnostics
	.word	pxe_unsupported		# force receive interrupt
#ifdef NEEDMCAST
	.word	pxe_get_mcast_addr	# get multicast address
#else
	.word	pxe_unsupported		# get multicast address
#endif
	.word	pxe_get_nic_type	# get network interface driver type
	.word	pxe_get_iface_info	# get interface info
	.word	pxe_isr			# handle hardware interrupt
	.word	pxe_stop_undi		# stop UNDI interface
	.word	pxe_get_state		# get UNDI state



/*
 **************************************************************************
 *
 * Stub for unsupported functions
 * Input:  ES:DI  -  pointer to parameter structure
 * Output: AX     -  error code
 * Registers changed: AX
 *
 */
pxe_unsupported:

	mov	ax,PXENV_STATUS_UNSUPPORTED
	ret



/*
 **************************************************************************
 *
 * START_UNDI. This just saves the BIOS parameters passed to the bootrom
 * and sets all interrupts required by the driver. The main UNDI handler
 * routine already checked that we are only called once.
 * Input:  ES:DI  -  pointer to parameter structure
 * Output: AX     -  error code
 * Registers changed: EAX, EBX, ECX, EDX, SI
 *
 */
pxe_start_undi:

	mov	word ptr [pkthandle],0xFFFF
	call	intstart		# set interrupt vectors
	xor	ax,ax			# return without error
9:	ret



/*
 **************************************************************************
 *
 * STOP_UNDI. This restores the interrupt vectors.
 * Input:  ES:DI  -  pointer to parameter structure
 * Output: AX     -  error code
 * Registers changed: AX, BX, CX, DX, SI
 *
 */
pxe_stop_undi:

	mov	ax,PXENV_STATUS_UNDI_INVALID_STATE
	cmp	byte ptr [initflag],FLAG_NOTINIT
	ja	9f
	call	closepkt		# close packet driver interface
	call	intstop			# restore interrupt vectors
	mov	ax,PXENV_STATUS_KEEP_UNDI
	jc	9f
	mov	byte ptr [initflag],FLAG_NOTSTART
	xor	ax,ax
9:	ret



/*
 **************************************************************************
 *
 * UNDI_STARTUP. Startup the packet driver interface. This will open a
 * packet driver handle and check that we are using a correct driver.
 * Input:  ES:DI  -  pointer to parameter structure
 * Output: AX     -  error code
 * Registers changed: AX, BX, CX, DX, SI
 *
 */
pxe_startup:

	push	es
	push	di
	mov	ax,PXENV_STATUS_UNDI_INVALID_STATE
	cmp	byte ptr [initflag],FLAG_NOTSTART
	jne	5f
	mov	ah,GET_PARAMETERS	# get parameters from packet
	call	callpkt			# driver. if driver is too old,
	jnc	1f			# assume default values
	mov	ax,ds
	mov	es,ax
	mov	di,offset def_info

1:	cmp	word ptr es:[di + 4],ETH_FRAME_MAX	# check MTU
	jb	8f
	cmp	byte ptr es:[di + 3],ETH_ALEN		# check MAC address size
	je	2f
8:	mov	ax,PXENV_STATUS_UNDI_NIC_INIT		# invalid packet driver
5:	jmp	9f

2:	mov	word ptr [pktinfo + 0],di	# save pointer to packet driver
	mov	word ptr [pktinfo + 2],es	# information structure
	call	openpkt				# open packet driver handle
	jc	8b

	push	ds
	mov	ax,(DRIVER_INFO * 256) + 0xFF
	mov	bx,[pkthandle]
	call	callpkt			# get packet driver info
	pop	ds
	jc	8b			# should never happen
	cmp	ch,CL_DEFAULT		# packet driver is not for ethernet
	jne	8b

	mov	[pktfunc],al		# save packet driver functionality
#if !defined(NEEDMCAST) && !defined(NEEDSETADDR)
	mov	word ptr [serviceflags],FLAGS_STANDARD
#else
	mov	bx,FLAGS_STANDARD
	cmp	al,FUNC_ALL		# check if function supported by
	je	3f			# packet driver
	cmp	al,FUNC_EXTENDED
	jne	4f
3:	or	bx,FLAGS_MCAST + FLAGS_SETADDR
4:	mov	[serviceflags],bx
#endif

	mov	ax,ds
	mov	es,ax
	mov	di,offset hwaddr
	mov	bx,[pkthandle]
	mov	cx,ETH_ALEN
	mov	ah,GET_ADDRESS		# determine hardware address
	call	callpkt
	jc	8b
	cmp	cx,ETH_ALEN		# check that ethernet address has
	jne	8b			# correct length

#ifdef NEEDSETADDR
	cld
	mov	si,offset hwaddr	# copy preset hardware address into
	mov	di,offset curaddr	# buffer for current address
	mov	cx,ETH_ALEN / 2
	rep	movsw
	call	setaddr			# check if setting is supported
	mov	byte ptr [addrflag],0
#endif
	mov	byte ptr [initflag],FLAG_NOTINIT
	xor	ax,ax			# return without error
9:	push	ax
	call	closepkt		# close packet driver handle again
	pop	ax
	pop	di
	pop	es
	ret



/*
 **************************************************************************
 *
 * UNDI_CLEANUP. This will remove the packet driver from memory and prepare
 * the UNDI driver for removal from memory.
 * Input:  ES:DI  -  pointer to parameter structure
 * Output: AX     -  error code
 * Registers changed: (E)AX, BX, CX, DX, SI
 *
 */
pxe_cleanup:

	mov	ax,PXENV_STATUS_UNDI_INVALID_STATE
	cmp	byte ptr [initflag],FLAG_NOTINIT
	ja	9f
	call	intset			# restore interrupts for packet driver
	jc	7f
	xor	ax,ax			# reset the packet driver interface
	mov	word ptr [pktfilter],ax	# clear packet filter
	call	resetpkt		# this also gets us a driver handle
	jc	7f
	mov	ah,TERMINATE		# terminate packet driver
	mov	bx,[pkthandle]
	call	callpkt
	jc	7f
	xor	ax,ax			# return without error
	jmp	8f

7:	mov	ax,PXENV_STATUS_KEEP_UNDI
8:	push	ax
	call	intreset		# restore interrupts
	pop	ax
	mov	word ptr [pkthandle],0xFFFF
	mov	dword ptr [pktvect],0
	mov	byte ptr [initflag],FLAG_NOTSTART
9:	ret



/*
 **************************************************************************
 *
 * Initialize the packet driver interface by setting all required access
 * types and handles.
 * Input:  ES:DI  -  pointer to parameter structure
 * Output: AX     -  error code
 * Registers changed: AX, BX, CX, DX, SI
 *
 */
pxe_initialize:

	mov	ax,PXENV_STATUS_UNDI_INVALID_STATE
	cmp	byte ptr [initflag],FLAG_NOTINIT
	jne	9f

/*
 * Reset the packet driver interface. This will also clear the receive
 * mode and multicast address list.
 */

	xor	ax,ax
	mov	word ptr [pktfilter],ax	# clear packet filter
	call	resetpkt
	mov	ax,PXENV_STATUS_UNDI_PHY_INIT
	jc	9f
	call	closepkt		# close packet driver again
	mov	byte ptr [initflag],FLAG_INIT
	xor	ax,ax			# return without error
9:	ret



/*
 **************************************************************************
 *
 * Shutdown the packet driver interface.
 * Input:  ES:DI  -  pointer to parameter structure
 * Output: AX     -  error code
 * Registers changed: AX, BX, CX, DX
 *
 */
pxe_shutdown:

	mov	ax,PXENV_STATUS_UNDI_INVALID_STATE
	cmp	byte ptr [initflag],FLAG_NOTINIT
	jbe	9f
	xor	dx,dx
	mov	[pktfilter],dx		# disable receiver
	call	setrcvmode
	call	closepkt		# close packet driver
	mov	byte ptr [initflag],FLAG_NOTINIT
	xor	ax,ax
9:	ret



/*
 **************************************************************************
 *
 * Reset the packet driver and reinitialize the interface.
 * Input:  ES:DI  -  pointer to parameter structure
 * Output: AX     -  error code
 * Registers changed: AX, BX, CX, DX, SI
 *
 */
pxe_reset_nic:

	mov	ax,PXENV_STATUS_UNDI_INVALID_STATE
	cmp	byte ptr [initflag],FLAG_NOTINIT
	jbe	9f
	mov	byte ptr [initflag],FLAG_NOTINIT

	mov	al,1
	call	resetpkt		# reset the packet driver interface
	mov	ax,PXENV_STATUS_UNDI_PHY_INIT
	jc	9f

#ifdef NEEDMCAST
	lea	si,o_ur_mcastbuf[di]
	call	setmcast
	mov	ax,PXENV_STATUS_UNDI_MCAST
	jc	9f			# reset the multicast address list
#endif

	mov	byte ptr [initflag],FLAG_OPEN
	xor	ax,ax			# return without error
9:	ret



/*
 **************************************************************************
 *
 * Open the network driver interface.
 * Input:  ES:DI  -  pointer to parameter structure
 * Output: AX     -  error code
 * Registers changed: AX, BX, CX, DX, SI
 *
 */
pxe_open:

	mov	ax,PXENV_STATUS_UNDI_INVALID_STATE
	cmp	byte ptr [initflag],FLAG_INIT
	jne	9f

	call	openpkt			# open packet driver interface
	mov	ax,PXENV_STATUS_FAILURE
	jc	9f

	mov	dx,word ptr es:o_uo_pktfilter[di]
	cmp	dx,[pktfilter]
	je	1f
	mov	[pktfilter],dx
	call	setrcvmode		# set receive mode packet filter

1:
#ifdef NEEDMCAST
	lea	si,o_uo_mcastbuf[di]
	call	setmcast		# set multicast address list
	mov	ax,PXENV_STATUS_UNDI_MCAST
	jc	9f
#endif

#ifdef NEEDSETADDR
	test	byte ptr [addrflag],0xFF
	jz	8f			# check if address changed
	push	es
	push	di
	call	setaddr
	pop	di
	pop	es
	mov	ax,PXENV_STATUS_UNDI_INV_HWADDR
	jc	9f
	mov	byte ptr [addrflag],0
#endif

8:	mov	byte ptr [initflag],FLAG_OPEN
	xor	ax,ax
9:	ret



/*
 **************************************************************************
 *
 * Close the network driver interface.
 * Input:  ES:DI  -  pointer to parameter structure
 * Output: AX     -  error code
 * Registers changed: AX, BX, CX, DX
 *
 */
pxe_close:

	mov	ax,PXENV_STATUS_UNDI_INVALID_STATE
	cmp	byte ptr [initflag],FLAG_OPEN
	jne	9f

	call	closepkt
	mov	ax,PXENV_STATUS_FAILURE
	jc	9f

	mov	byte ptr [initflag],FLAG_INIT
	xor	ax,ax			# return without error
9:	ret



/*
 **************************************************************************
 *
 * Transmit a packet
 * Input:  ES:DI  -  pointer to parameter structure
 * Output: AX     -  error code
 * Registers changed: AX, BX, CX, DX, SI
 *
 */
pxe_transmit:

	push	es
	push	di
	push	bp
	mov	ax,PXENV_STATUS_UNDI_INVALID_STATE
	cmp	byte ptr [initflag],FLAG_OPEN
	jne	9f

/*
 * According to the PXE specification it should never happen that the transmit
 * function gets called while an interrupt hasn't been processed completely.
 * However, with the netboot base code, this is indeed possible. How should we
 * handle this case? Just bailing out will terminate the boot process, so it's
 * not a real option. Actually, this problem has to be solved within the base
 * code, and not here. But we try a solution: just re-enable the hardware
 * interrupt to avoid a lockup.
 */

#if MASKINT == 1

	pushf
	cli
	mov	byte ptr [intdisable],1
	cmp	byte ptr [intcount],0
	je	2f			# check if interrupts are pending
	cmp	byte ptr [isrflag],0
	je	1f			# if currently processing any
	popf				# interrupts it is impossible to
	jmp	8f			# send anything

1:	call	endhwint		# if any interrupt is currently
	mov	byte ptr [intcount],0	# pending, reenable interrupt
2:	popf				# processing

#endif

	cld
	push	ds
	push	es
	mov	ax,ds			# DS:BX  -  ptr to parameter structure
	mov	es,ax			# ES:DI  -  ptr to send buffer
	mov	bx,di
	mov	di,offset writebuf
	pop	ds
	xor	bp,bp			# BP  -  total size of send buffer

/*
 * First setup the MAC level header. If the protocol value in the parameter
 * structure is 0, the upper layer has already filled in the MAC header.
 * Otherwise, if the transmit flag is 0, we have to fill in the broadcast
 * address for the destination address. Otherwise the hardware destination
 * address can be found in the protocol structure.
 */

	mov	dl,byte ptr o_ut_protocol[bx]
	or	dl,dl
	jz	4f

	mov	cx,ETH_ALEN / 2
	cmp	byte ptr o_ut_xmtflag[bx],UNDI_XMT_DESTADDR
	je	1f
	mov	ax,0xFFFF		# set destination address to broadcast
	rep	stosw
	jmp	2f

1:	push	ds
	lds	si,o_ut_dest_addr[bx]	# copy destination address into send
	rep	movsw			# buffer
	pop	ds

2:	push	ds
	mov	ax,es
	mov	ds,ax
#ifdef NEEDSETADDR
	mov	si,offset curaddr
#else
	mov	si,offset hwaddr
#endif
	mov	cx,ETH_ALEN / 2
	rep	movsw			# copy source address into send buffer
	pop	ds

	mov	ax,ETHERTYPE_IP
	cmp	dl,UNDI_PROT_IP		# determine type of packet
	je	3f
	mov	ax,ETHERTYPE_ARP
	cmp	dl,UNDI_PROT_ARP
	je	3f
	mov	ax,ETHERTYPE_RARP
3:	xchg	al,ah			# write packet type into MAC header
	stosw
	add	bp,ETH_HLEN		# adjust buffer size to MAC header size

/*
 * The parameter structure contains a couple of buffers. One of them is the
 * so called immediate buffer. Copy it into the send buffer.
 */

4:	lds	bx,o_ut_tbd[bx]
	mov	cx,o_tbd_xmtlength[bx]
	jcxz	5f
	add	bp,cx
	cmp	bp,ETH_FRAME_MAX
	ja	1f
	push	ds
	lds	si,o_tbd_xmtbuffer[bx]
	shr	cx,1
	rep	movsw
	jnc	2f
	movsb
2:	pop	ds

/*
 * Now copy all the different transmit buffers into the send buffer.
 */

5:	mov	dx,o_tbd_blocks[bx]
	or	dx,dx
	jz	3f

	lea	bx,o_tbd_datablock[bx]
6:	mov	cx,o_tbd_tdlength[bx]
	jcxz	4f
	add	bp,cx
	cmp	bp,ETH_FRAME_MAX
	ja	1f
	push	ds
	lds	si,[bx + o_tbd_tdaddr]
	shr	cx,1
	rep
	movsw
	jnc	5f
	movsb
5:	pop	ds
4:	add	bx,datablk_size		# continue with next buffer
	dec	dx
	jnz	6b
	jmp	3f

/*
 * Handle transmit errors
 */

1:	pop	ds
8:	mov	ax,PXENV_STATUS_UNDI_TRANSMIT

#if MASKINT == 1
	jmp	0f
#else
	jmp	9f
#endif

/*
 * We can now fillup the send buffer to the minimum size and send it using
 * the packet driver
 */

3:	cmp	bp,ETH_FRAME_MIN
	jae	7f
	mov	cx,ETH_FRAME_MIN
	sub	cx,bp
	xor	al,al			# pad send buffer to minimum size
	rep	stosb
	mov	bp,ETH_FRAME_MIN

7:	pop	ds
	mov	cx,XMT_RETRY
2:	push	cx
	mov	si,offset writebuf
	mov	cx,bp
	mov	bx,[pkthandle]		# send buffer using the packet driver
	mov	ah,SEND_PKT
	call	callpkt
	pop	cx
	jnc	3f
	cmp	dh,ERR_CANT_SEND	# if we cant send the packet, retry it
	jne	8b			# a couple of times, as the sender might
	mov	bx,XMT_WAIT		# just be busy right now
	call	wait10ms
	loop	2b
	jmp	8b

3:
#ifdef NEEDSTAT
	inc	dword ptr [xmtgood]	# update the statistics
#endif
	xor	ax,ax			# return without error
#if MASKINT == 1
0:	mov	byte ptr [intdisable],0
#endif

9:	pop	bp
	pop	di
	pop	es
	ret



#ifdef NEEDMCAST
/*
 **************************************************************************
 *
 * Set multicast address list
 * Input:  ES:DI  -  pointer to parameter structure
 * Output: AX     -  error code
 * Registers changed: AX, BX, CX, DX, SI
 *
 */
pxe_set_multicast:

	mov	ax,PXENV_STATUS_UNDI_INVALID_STATE
	cmp	byte ptr [initflag],FLAG_OPEN
	jne	9f
	lea	si,o_usma_mcastbuf[di]
	call	setmcast
	mov	ax,PXENV_STATUS_UNDI_MCAST
	jc	9f
	xor	ax,ax
9:	ret
#endif



#ifdef NEEDSETADDR
/*
 **************************************************************************
 *
 * Set station address
 * Input:  ES:DI  -  pointer to parameter structure
 * Output: AX     -  error code
 * Registers changed: (E)AX, BX, CX
 *
 */
pxe_set_addr:

#if ETH_ALEN == 6
# define pseta0	0
#else
# define pseta0	1
#endif

	mov	ax,PXENV_STATUS_UNDI_INVALID_STATE
	cmp	byte ptr [initflag],FLAG_INIT
	jne	9f			# is UNDI initialized already?
	mov	ax,PXENV_STATUS_UNSUPPORTED
	test	word ptr [serviceflags],FLAGS_SETADDR
	jz	9f			# is this function supported?

#if pseta0 == 0
	mov	eax,dword ptr es:o_ussa_addr[di + 0]
	mov	dword ptr [curaddr + 0],eax
	mov	ax,word ptr es:o_ussa_addr[di + 4]
	mov	word ptr [curaddr + 4],ax
#else
	xor	bx,bx
	mov	cx,ETH_ALEN / 2
1:	mov	ax,word ptr es:o_ussa_addr[di + bx]
	mov	word ptr [bx + curaddr],ax
	add	bx,2
	loop	1b
#endif
	inc	byte ptr [addrflag]
	xor	ax,ax
9:	ret

# undef pseta0
#endif



/*
 **************************************************************************
 *
 * Set packet filter
 * Input:  ES:DI  -  pointer to parameter structure
 * Output: AX     -  error code
 * Registers changed: AX, BX, CX, DX
 *
 */
pxe_set_filter:

	mov	ax,PXENV_STATUS_UNDI_INVALID_STATE
	cmp	byte ptr [initflag],FLAG_OPEN
	jne	9f
	mov	dx,es:o_uo_pktfilter[di]
	cmp	dx,[pktfilter]
	je	8f
	mov	[pktfilter],dx
	call	setrcvmode
8:	xor	ax,ax
9:	ret



/*
 **************************************************************************
 *
 * Get information about network driver
 * Input:  ES:DI  -  pointer to parameter structure
 * Output: AX     -  error code
 * Registers changed: AX, BX, CX, SI
 *
 */
pxe_get_info:

	mov	ax,PXENV_STATUS_UNDI_INVALID_STATE
	cmp	byte ptr [initflag],FLAG_INIT
	jb	9f

	cld
	push	di
	push	ds
	add	di,_I(o_ugi_intnumber)
	mov	ax,[irqnum]		# get interrupt IRQ number
	stosw
	lds	bx,[pktinfo]
	mov	ax,[bx + 4]		# get MTU (without MAC header size)
	sub	ax,ETH_HLEN
	stosw
	mov	ax,UNDI_TYPE_ETHER	# get ethernet hardware type
	stosw
	mov	ax,ETH_ALEN		# get hardware address length
	stosw
	pop	ds

#ifdef NEEDSETADDR
	mov	si,offset curaddr
#else
	mov	si,offset hwaddr
#endif
	mov	cx,ETH_ALEN / 2		# copy current hardware address
	rep	movsw
	add	di,UNDI_ADDR_LEN - ETH_ALEN
	mov	si,offset hwaddr
	mov	cx,ETH_ALEN / 2		# copy default hardware address
	rep	movsw
	pop	di

	mov	ax,1			# save size of send/receive queues
	mov	es:o_ugi_rxbufcnt[di],ax
	mov	es:o_ugi_txbufcnt[di],ax
	xor	ax,ax
9:	ret



#ifdef NEEDSTAT
/*
 **************************************************************************
 *
 * Read network driver statistics
 * Input:  ES:DI  -  pointer to parameter structure
 * Output: AX     -  error code
 * Registers changed: (E)AX
 *
 */
pxe_get_stat:

	mov	eax,[xmtgood]
	mov	es:o_ugs_xmtgood[di],eax
	mov	eax,[rcvgood]
	mov	es:o_ugs_rcvgood[di],eax
	mov	eax,[rcvresource]
	mov	es:o_ugs_rcvreserr[di],eax
	xor	ax,ax			# return without error
	ret
#endif



#ifdef NEEDSTAT
/*
 **************************************************************************
 *
 * Clear network driver statistics
 * Input:  ES:DI  -  pointer to parameter structure
 * Output: AX     -  error code
 * Registers changed: (E)AX
 *
 */
pxe_clear_stat:

	xor	eax,eax
	mov	[xmtgood],eax
	mov	[rcvgood],eax
	mov	[rcvresource],eax
	ret
#endif



#ifdef NEEDMCAST
/*
 **************************************************************************
 *
 * Determine hardware multicast address for IP multicast address.
 * Input:  ES:DI  -  pointer to parameter structure
 * Output: AX     -  error code
 * Registers changed: AX, CX, SI
 *
 */
pxe_get_mcast_addr:

	lea	bx,o_ugma_inetaddr[di]
	mov	al,es:[bx]
	and	al,IP_CLASS_D_MASK	# check that the IP address really is
	cmp	al,IP_CLASS_D_VAL	# a multicast address
	mov	ax,PXENV_STATUS_UNDI_INV_HWADDR
	jne	9f

	cld
	push	di
	lea	di,o_ugma_hwaddr[di]
	mov	ax,HW_MCAST_2 * 256 + HW_MCAST_1
	stosw
	mov	al,HW_MCAST_3		# set hardware multicast for ethernet
	stosb				# devices
	mov	al,es:[bx + 1]
	and	al,0x7F			# only 23 bits of the IP address will
	stosb				# be copied
	mov	ax,es:[bx + 2]
	stosw
	pop	di
	xor	ax,ax			# return without error
9:	ret

#endif



/*
 **************************************************************************
 *
 * Return NIC type information.
 * Input:  ES:DI  -  pointer to parameter structure
 * Output: AX     -  error code
 * Registers changed: AX, CX, SI
 *
 */
pxe_get_nic_type:

	cld
	push	di
	mov	si,offset devinfo
	mov	cx,getnic_size		# just copy the hardware info structure
	rep	movsb			# into destination parameter structure
	pop	di
	xor	ax,ax
	ret



/*
 **************************************************************************
 *
 * Return interface info.
 * Input:  ES:DI  -  pointer to parameter structure
 * Output: AX     -  error code
 * Registers changed: AX, CX, SI
 *
 */
pxe_get_iface_info:

	cld
	push	di
	xor	al,al
	mov	cx,getiface_size
	rep	stosb
	lea	di,o_ugii_ifacetype[di - getiface_size]
	mov	si,offset iface_type
	mov	cx,offset iface_type_end - offset iface_type
	rep	movsb
	pop	di
	mov	ax,[serviceflags]
	mov	es:o_ugii_serviceflags[di],ax
	xor	ax,ax
	ret



/*
 **************************************************************************
 *
 * Handle hardware interrupt functions including reading the received
 * data from the network driver.
 * Input:  ES:DI  -  pointer to parameter structure
 * Output: AX     -  error code
 * Registers changed: AX, BX
 *
 */
pxe_isr:

/*
 * Dont check for the initflag status. This will be done in the receiver
 * routine. Otherwise, we would block the interrupt system on the NIC by
 * not calling the packet driver hardware interrupt handler.
 */

	mov	bx,es:o_uisr_funcflag[di]
	cmp	bx,UNDI_ISR_IN_START
	je	1f
	pushf					# we have to disable interrupts
	cli					# here
	cmp	bx,UNDI_ISR_IN_PROCESS
	je	3f
	test	byte ptr [isrflag],0xFF		# IN_GET_NEXT is only valid
	jz	2f				# if following a IN_PROCESS
	cmp	bx,UNDI_ISR_IN_GET_NEXT		# we only have one rx buffer so
	je	5f				# terminate ISR on IN_GET_NEXT
2:	popf
	mov	ax,PXENV_STATUS_FAILURE
	jmp	9f

/*
 * Handle function UNDI_ISR_IN_START. It will let the packet driver handle
 * the interrupt.
 */

1:	call	dohwint
	jc	4f

#if MASKINT == 1
	test	byte ptr [intdisable],0xFF
	jz	6f
	call	endhwint
	jmp	4f
#endif

6:	inc	byte ptr [intcount]
	mov	ax,UNDI_ISR_OUT_OURS
	jmp	8f

4:	mov	ax,UNDI_ISR_OUT_NOT_OURS
	jmp	8f

/*
 * Handle function UNDI_ISR_IN_PROCESS. It returns the address of the receive
 * buffer to the kernel.
 */

3:	mov	al,1
	xchg	al,[isrflag]			# check that we are not inside
	or	al,al				# our own handler
	mov	ax,UNDI_ISR_OUT_BUSY
	jnz	7f
	mov	ax,[readsize]
	or	ax,ax				# check that we have something
	jz	5f				# to receive
	popf
	mov	word ptr es:o_uisr_buflength[di],ax
	mov	word ptr es:o_uisr_framelength[di],ax
	mov	word ptr es:o_uisr_headerlength[di],ETH_HLEN
	mov	ax,[pkttype]
	mov	byte ptr es:o_uisr_pkttype[di],al
	mov	bx,offset readbuf
	mov	word ptr es:o_uisr_frame[di + 0],bx
	mov	word ptr es:o_uisr_frame[di + 2],ds
	mov	ax,[bx + ETH_ALEN * 2]
	xchg	al,ah
	mov	bx,UNDI_PROT_IP
	cmp	ax,ETHERTYPE_IP
	je	4f
	mov	bx,UNDI_PROT_ARP
	cmp	ax,ETHERTYPE_ARP
	je	4f
	mov	bx,UNDI_PROT_RARP
	cmp	ax,ETHERTYPE_RARP
	je	4f
	mov	bx,UNDI_PROT_OTHERS
4:	mov	es:o_uisr_prottype[di],bx
	mov	ax,UNDI_ISR_OUT_RECEIVE
	jmp	8f

/*
 * Terminate hardware interrupt processing.
 */

5:	cmp	byte ptr [intcount],0
	je	6f
	call	endhwint
6:	mov	word ptr [readsize],0
	mov	byte ptr [isrflag],0
	mov	byte ptr [intcount],0
	mov	ax,UNDI_ISR_OUT_DONE		# terminate ISR handling
7:	popf
8:	mov	es:o_uisr_funcflag[di],ax
	xor	ax,ax
9:	ret



/*
 **************************************************************************
 *
 * Return UNDI state.
 * Input:  ES:DI  -  pointer to parameter structure
 * Output: AX     -  error code
 * Registers changed: AX
 *
 */
pxe_get_state:

	mov	al,[initflag]
	mov	es:o_ugs_state[di],al
	xor	ax,ax
	ret



/*
 **************************************************************************
 *
 * This routine gets called by the packet driver when a new packet arrives.
 * Input:  AX  -  function number: 0 = return buffer pointer, 1 = buffer filled
 *         BX  -  handle
 *         CX  -  length of data received
 *         DS:SI  -  pointer to receive buffer (only when AX = 1)
 * Output: ES:DI  -  pointer to receive buffer (only when AX = 0)
 * Registers changed: AX, ES, DI
 *
 */
receiver:

	push	ds
	mov	ds,cs:o_desc_segment[pxestruct + pxe_undi_data_sel]
	or	ax,ax				# check function code
	jnz	1f

/*
 * Return address of receive buffer in ES:DI. Discard the packet if the
 * receive buffer is not empty or the receiver is disabled.
 */

	cmp	byte ptr [initflag],FLAG_OPEN	# discard packet if driver not
	jne	2f				# yet initialized
	cmp	cx,ETH_FRAME_MAX		# received data block too large?
	ja	2f
	mov	ax,[pktfilter]			# receiver disabled?
	or	ax,ax
	jz	2f
	mov	ax,[readsize]			# buffer free?
	or	ax,ax
	jnz	2f
	mov	es,cs:o_desc_segment[pxestruct + pxe_undi_data_sel]
	mov	di,offset readbuf		# return pointer to read buffer
	jmp	9f

2:	xor	di,di
	mov	es,di				# discard packet
#ifdef NEEDSTAT
3:	inc	dword ptr [rcvresource]
#endif
	jmp	9f

/*
 * The packet driver has copied the received packet into our buffer, so
 * we can now check if the packet is for us at all.
 */

1:	mov	si,offset readbuf
	call	getaddrtype
	mov	[pkttype],bx
	mov	ax,[pktfilter]
	call	checktype
#ifdef NEEDSTAT
	jc	3b
#else
	jc	9f
#endif
	mov	[readsize],cx			# save size of packet
#ifdef NEEDSTAT
	inc	dword ptr [rcvgood]
#endif
9:	pop	ds
	lret



/*
 **************************************************************************
 *
 * Determine type of hardware address
 * Input:  DS:SI  -  pointer to MAC header hardware address
 * Output: BX     -  type of hardware address (UNDI_PKT_*)
 * Registers changed: AX, BX
 *
 */
getaddrtype:

/*
 * Check if the first bit of the hardware address is zero. If it is, the
 * address has to be directed.
 */

	test	byte ptr [si],0x01
	jz	8f

/*
 * Now check if we have a broadcast address.
 */

	mov	bx,UNDI_PKT_BROADCAST
#if ETH_ALEN == 6
	mov	ax,[si + 0]
	and	ax,[si + 2]
	and	ax,[si + 4]
	inc	ax
#else
	cld
	push	cx
	mov	cx,ETH_ALEN
	mov	ah,0xFF
1:	lodsb
	and	ah,al
	loop	1b
	pop	cx
	sub	si,ETH_ALEN
	inc	ah
#endif
	jz	9f

/*
 * None of the conditions above applied, so check for a multicast address.
 * All others are either directed or promiscuous.
 */

#ifdef NEEDMCAST
	mov	bx,UNDI_PKT_MULTICAST
	cmp	word ptr [si],HW_MCAST_2 * 256 + HW_MCAST_1
	jne	8f
	cmp	byte ptr [si + 2],HW_MCAST_3
	je	9f
#endif
8:	mov	bx,UNDI_PKT_DIRECTED
9:	ret



/*
 **************************************************************************
 *
 * Check if a received packet is for us.
 * Input:  DS:SI  -  pointer to MAC header hardware address
 *         BX     -  type of hardware address (UNDI_PKT_*)
 *         AX     -  packet filter value
 * Output: Carry set if packet is to be discarded
 * Registers changed: (E)AX, BX, DX
 *
 */
checktype:

#if ETH_ALEN == 6
# define chkt0	0
#else
# define chkt0	1
#endif

/*
 * With promiscuous mode all packets are accepted. When no mode is
 * selected, no packets are accepted.
 */

	test	ax,UNDI_FLTR_PROMISC
	jnz	9f
	or	ax,ax
	jz	8f

/*
 * Check if we have a broadcast address and are willing to accept broad-
 * casts.
 */

	cmp	bx,UNDI_PKT_BROADCAST
	jne	1f
	test	ax,UNDI_FLTR_BROADCAST
	jnz	9f
	jmp	8f

/*
 * Check if this packet is directed, and matches our own hardware address.
 */

1:	cmp	bx,UNDI_PKT_DIRECTED
	jne	2f
	test	ax,UNDI_FLTR_DIRECTED
	jz	8f

#if chkt0 == 0

	mov	eax,dword ptr [si + 0]
# ifdef NEEDSETADDR
	cmp	eax,dword ptr [curaddr + 0]
# else
	cmp	eax,dword ptr [hwaddr + 0]
# endif
	jne	8f
	mov	ax,word ptr [si + 4]
# ifdef NEEDSETADDR
	cmp	ax,word ptr [curaddr + 4]
# else
	cmp	ax,word ptr [hwaddr + 4]
# endif
	je	9f

#else

	cld
	push	es
	mov	ax,ds
	mov	es,ax			# temporarily save all registers
	mov	ax,si
	mov	dx,di
	mov	bx,cx
# ifdef NEEDSETADDR
	mov	di,offset curaddr
# else
	mov	di,offset hwaddr
# endif
	mov	cx,ETH_ALEN / 2
	repe	cmpsw			# check if the address is our own
	pop	es
	mov	si,ax
	mov	di,dx			# restore registers
	mov	cx,bx
	clc
	je	9f

#endif

	jmp	8f

/*
 * Check if this packet is multicast and we want to accept multicasts and
 * the address is in our multicast list.
 */

2:
#ifdef NEEDMCAST
	cmp	bx,UNDI_PKT_MULTICAST
	jne	8f
	test	ax,UNDI_FLTR_DIRECTED
	jz	8f
	call	getmcbit		# compute offset into multicast
	test	[bx],al			# buffer
	jnz	9f
#endif
8:	stc
9:	ret

#undef chkt0



#ifdef NEEDMCAST
/*
 **************************************************************************
 *
 * Compute bit position in multicast buffer for a given hardware address
 * Input:  DS:SI  -  pointer to hardware address
 * Output: BX     -  offset to byte in multicast buffer
 *         AL     -  bit mask
 * Registers changed: AX, BX, DX
 *
 */
getmcbit:

	cld
	push	cx
	mov	ch,ETH_ALEN
	mov	dx,0xFFFF		# DX:BX gets CRC value
	mov	bx,0xFFFF

/*
 * Scan through all bytes in the hardware address and compute the CRC
 * value.
 */

1:	lodsb
	xor	ah,ah
	mov	cl,8			# do 8 bits

2:	shld	dx,bx,1			# shift DX:BX
	rcl	ah,1			# carry is at bottom of AH
	xor	ah,al			# xor with lsb of data
	rcr	ah,1			# and put in carry bit
	jnc	3f

/*
 * The polynome is:
 *
 *  x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 +
 *  x^7 + x^5 + x^4 + x^2 + x^1
 */

	xor	dx,0b0000010011000001
	xor	bx,0b0001110110110110 + 1	# plus one for end-around carry
3:	shr	al,1				# shift the data
	dec	cl
	jnz	2b				# scan through all bits
	dec	ch
	jnz	1b				# scan through all bytes

/*
 * Compute the byte offset into the multicast buffer and the bit mask.
 */

	mov	bl,dh			# get most significant 8 bits
	rol	bl,3
	and	bl,0x07
	xor	bh,bh			# make BX into an index to the byte
	add	bx,offset mcastbuf

	mov	cl,dh			# get most significant 8 bits
	ror	cl,2
	and	cl,0x07
	mov	al,0x01
	shl	al,cl			# set the correct bit
	sub	si,ETH_ALEN		# restore SI
	pop	cx
	ret
#endif



#ifdef NEEDMCAST
/*
 **************************************************************************
 *
 * Set multicast addresses from UNDI address list.
 * Input:  ES:SI  -  pointer to UNDI multicast address structure
 * Output: Carry flag set if error
 * Registers changed: AX, BX, CX, DX, SI
 *
 */
setmcast:

	cld
	push	ds
	push	es
	push	ds
	mov	bx,es			# let DS:SI point to multicast list
	mov	ds,bx
	pop	es

	push	di
	mov	di,offset mcastbuf	# clear multicast buffer
	mov	cx,MCAST_BYTES / 2
	xor	ax,ax
	rep	stosw
	pop	di

	or	bx,si			# do we have multicast addresses at all
	jz	8f
	lodsw				# get number of addresses in list
	mov	cx,ax
	jcxz	8f			# nothing else to do
	test	word ptr es:[serviceflags],FLAGS_MCAST
	stc
	jz	9f			# is multicast supported?

1:	call	getaddrtype		# check that the address really is
	cmp	bx,UNDI_PKT_MULTICAST	# a multicast address
	stc		
	jne	9f
	call	getmcbit		# get offset into multicast buffer
	or	es:[bx],al		# set multicast address bit
	add	si,UNDI_ADDR_LEN - ETH_ALEN
	loop	1b
8:	clc
9:	pop	es
	pop	ds
	ret
#endif



/*
 **************************************************************************
 *
 * Set packet receive mode. This is not as easy as it looks because some
 * packet drivers dont support this at all, and others just dont support
 * certain packet filter values. Unfortunately, this is not predictable.
 * Also, with some packet drivers (notably RTL-8139), after turning off
 * the receiver there is no way to turn it on again. Sometimes the packet
 * driver will also turn off the transmitter. So we have to avoid turning
 * off the receiver with those packet drivers. On the other hand, some
 * other packet driver (like the 3C905C driver) need to get the receiver
 * turned off explicitely to avoid overflowing the interrupt engine when
 * shutting down the UNDI driver. We use the DRVFLG_NOMODE0 flag for to
 * determine the correct behaviour.
 * However, we do packet filtering by software in the receiver routine,
 * and therefore have to set the packet driver filter value just to
 * reduce the number of hardware interrupt frequency. In addition, the
 * UNDI filter values dont directly match the packet driver values so we
 * have to do some conversion.
 * Input:  DX  -  UNDI packet filter value
 * Output: none
 * Registers changed: AX, BX, CX
 *
 */
setrcvmode:

	mov	al,[pktfunc]
	cmp	al,FUNC_EXTENDED
	je	1f			# check that packet driver supports
	cmp	al,FUNC_ALL		# setting the receive mode
	jne	9f

1:	mov	bx,[pkthandle]
	test	dx,UNDI_FLTR_PROMISC	# promiscious mode means to read every
	jnz	8f			# packet on the line

#ifdef NEEDMCAST
	test	word ptr [serviceflags],FLAGS_MCAST
	jz	2f			# if we have multicast support, the
	test	dx,UNDI_FLTR_DIRECTED	# directed flag means to receive all
	jnz	7f			# directed and multicast packets
#endif

2:	test	dx,UNDI_FLTR_BROADCAST	# the broadcast flag always implies
	jnz	6f			# the directed flag for the packet
	test	dx,UNDI_FLTR_DIRECTED	# driver
	jnz	5f
	test	word ptr cs:[drvflg],DRVFLG_NOMODE0
	jnz	5f			# check if we can use receive mode 0

/*
 * Set the lowest possible receiver mode.
 */

	mov	ah,SET_RCV_MODE
	mov	cx,MODE_RCVOFF
	call	callpkt
	jnc	9f
5:	mov	ah,SET_RCV_MODE
	mov	cx,MODE_DIRECTED
	call	callpkt
	jnc	9f
6:	mov	ah,SET_RCV_MODE
	mov	cx,MODE_BROADCAST
	call	callpkt
	jnc	9f
#ifdef NEEDMCAST
7:	mov	ah,SET_RCV_MODE
	mov	cx,MODE_MCASTALL
	call	callpkt
	jnc	9f
#endif
8:	mov	ah,SET_RCV_MODE
	mov	cx,MODE_ALL
	call	callpkt
9:	ret



#ifdef NEEDSETADDR
/*
 **************************************************************************
 *
 * Set adapter hardware address
 * Input:  none
 * Output: Carry flag set if error
 * Registers changed: AX, CX, DI, ES
 *
 */
setaddr:

	test	word ptr [serviceflags],FLAGS_SETADDR
	jz	9f			# is this function supported?
	mov	ax,ds
	mov	es,ax
	mov	di,offset curaddr
	mov	cx,ETH_ALEN
	mov	ah,SET_ADDRESS
	call	callpkt
	jnc	9f
	cmp	dh,ERR_BAD_ADDRESS	# check if function supported?
	stc
	je	9f
	and	word ptr [serviceflags],_NOT(FLAGS_SETADDR)
9:	ret
#endif



/*
 **************************************************************************
 *
 * Open packet driver interface.
 * Input:  None
 * Output: Carry flag set if error
 * Registers changed: AX, BX, CX, DX, SI
 *
 */
openpkt:

	push	es
	push	di
	cmp	word ptr [pkthandle],0xFFFF
	clc
	jne	9f
	mov	ax,cs
	mov	es,ax
	mov	di,offset receiver
	mov	ax,(ACCESS_TYPE * 256) + CL_DEFAULT
	mov	bx,TYPE_DEFAULT
	xor	dl,dl				# set general packet handle
	xor	cx,cx				# this will setup the packet
	call	callpkt				# driver to receive packets
	jc	9f				# with any packet type
	mov	[pkthandle],ax
9:	pop	di
	pop	es
	ret



/*
 **************************************************************************
 *
 * Close packet driver interface.
 * Input:  None
 * Output: Carry flag set if error
 * Registers changed: AX, BX
 *
 */
closepkt:

	mov	bx,[pkthandle]
	cmp	bx,0xFFFF
	je	9f
	mov	ah,RELEASE_TYPE
	call	callpkt
	jc	9f
	mov	word ptr [pkthandle],0xFFFF
9:	ret



/*
 **************************************************************************
 *
 * Reset packet driver interface. Some packet drivers (pcipd.com for
 * RTL8029 cards for example) incorrectly :-( hang the network card
 * completely, so that it isnt able to receive anymore. We therefore
 * disable the hardware reset feature and just reprogram the packet
 * driver with some default values. Note that this routine has to
 * return with an open packet driver handle!
 * Input:  AL  -  non-zero if we have to physically reset the network card
 * Output: Carry flag set if error
 * Registers changed: AX, BX, CX, DX, SI
 *
 */
resetpkt:

/*
 * Reset the packet driver interface
 */

	push	ax
	call	openpkt			# get us a packet driver handle
	pop	ax
	jc	9f
	or	al,al			# check if we have to actually reset
	jz	1f			# the interface
	test	word ptr cs:[drvflg],DRVFLG_NORESET
	jnz	1f			# check if we can use the reset call
	mov	ah,RESET_INTERFACE
	mov	bx,[pkthandle]
	call	callpkt
	jc	9f

/*
 * Clear the multicast list
 */

1:
#ifdef NEEDMCAST
	push	es
	xor	si,si
	mov	es,si			# clear the multicast buffer
	call	setmcast
	pop	es
#endif

/*
 * Restore the packet filter and hardware address
 */

	mov	dx,[pktfilter]		# reset packet filter
	call	setrcvmode

#ifdef NEEDSETADDR
	cld
	push	es
	push	di
	mov	ax,ds
	mov	es,ax
	mov	si,offset hwaddr	# copy preset hardware address into
	mov	di,offset curaddr	# buffer for current address
	mov	cx,ETH_ALEN / 2
	rep	movsw
	call	setaddr			# set hardware address
	mov	byte ptr [addrflag],0
	pop	di
	pop	es
#endif
9:	ret



/*
 **************************************************************************
 *
 * Call packet driver interrupt
 * Input:  AH  -  function code
 * Output: depends on function called
 * Registers changed: depends on function called
 *
 */
callpkt:

	cmp	dword ptr [pktvect],0
	jne	1f
	mov	dh,ERR_BAD_COMMAND	# check if we have a packet driver
	stc				# at all
	ret

1:	push	cs
	push	ax			# set stack to simulate an INT
	pushf
	pop	ax
	push	bp
	mov	bp,sp
	xchg	ax,[bp + 6]		# set flags
	xchg	ax,[bp + 2]		# set return address and restore AX
	pop	bp
	ljmp	[pktvect]		# call packet driver




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

	.end

