/*
 **************************************************************************
 *
 * Network driver interface for netboot bootrom
 *
 * Module:  protman.S86
 * Purpose: Protocol manager support for NDIS interface
 * Entries: pm_init
 *
 **************************************************************************
 *
 * 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: protman.S86,v 1.7 2007/01/06 18:30:49 gkminix Exp $
 *
 */


/*
 **************************************************************************
 *
 * Include assembler macros:
 *
 */
#include <common.i86>
#include <romconfig.i86>
#include <system/bootrom.i86>
#include <system/bios.i86>
#include "ndis.i86"
#include "ndisdos.i86"

.file __FILE__
.line __LINE__



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

	.extern	alt_handler		# alternative DOS function handler

	.global	mac_cct


	.lcomm	pi_seg,2		# segment of PROTOCOL.INI image
	.lcomm	pi_size,2		# size of PROTOCOL.INI image
	.lcomm	mac_cct,4		# far ptr to MAC CCT




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

devpfx:	.ascii	"\\dev\\"		# device name prefix
pmfile:	.asciz	"protman$"		# device name of protocol manager




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

	.global	pm_init			# define entry points



/*
 **************************************************************************
 *
 * Initialize the protocol manager support for the NDIS interface. This
 * involves copying the PROTOCOL.INI image into place, adjust all far
 * pointers in it and set our own DOS function handler.
 * Input:  ES:SI  -  pointer to PROTOCOL.INI image
 *         DX     -  first usable paragraph in data area
 * Output: ES:SI  -  pointer to first byte after PROTOCOL.INI image
 *         DX     -  first usable paragraph after PROTOCOL.INI image
 * Output: Carry flag if fatal error
 * Registers changed: AX, BX, CX, DX, SI, DI, ES
 *
 */
pm_init:

/*
 * First copy the PROTOCOL.INI image into our private data segment
 */

	cld
	mov	[pi_seg],dx		# save segment to new PROTOCOL.INI image
	push	ds
	push	es
	mov	ax,es			# set DS:SI to source image
	mov	es,dx			# set ES:DI to destination area
	mov	ds,ax
	xor	di,di
	lodsw				# get size of PROTOCOL.INI image
	mov	cx,ax
	rep	movsb			# copy PROTOCOL.INI image
	mov	bx,es			# save ES:DI for later
	mov	cx,di
	add	di,0x000F		# adjust end address to paragraph
	rcr	di,1			# boundary
	shr	di,3
	add	dx,di			# compute first segment after new image
	pop	es			# ES:SI and DX now contain the return
	pop	ds			# values
	mov	[pi_size],ax		# save size of PROTOCOL.INI image
	mov	ax,cs
	cmp	dx,ax			# check if image is too large
	jae	8f

/*
 * Now set all far pointers in the new image. makerom placed only the
 * offsets into the pointer variables. At this point, BX contains the
 * offset to the first byte after the PROTOCOL.INI image. We have to
 * leave ES:SI and DX unchanged here.
 */

	push	ds
	mov	ds,bx			# set DS:DI to beginning of new image
	xor	di,di			# copy

1:	cmp	di,cx			# check if past end of new image,
	ja	7f			# should never happen
	mov	bx,[di + 0]		# get offset to next module header
	or	bx,bx
	jz	2f			# skip adjusting segment for last header
	mov	[di + 2],ds		# save new segment
2:	mov	ax,[di + 4]		# get offset to previous module header
	or	ax,ax
	jz	3f			# skip adjusting if first header
	mov	[di + 6],ds		# save new segment

3:	add	di,24			# set DS:DI to first keyword entry
	push	bx
	push	cx
	or	bx,bx			# set end offset of current module
	jz	4f
	mov	cx,bx
4:	cmp	di,cx			# check if past end of current module
	ja	6f
	mov	bx,[di + 0]		# get offset to next keyword
	or	bx,bx
	jz	5f
	mov	[di + 2],ds		# save new segment
5:	mov	ax,[di + 4]		# get offset to previous keyword
	or	ax,ax
	jz	0f
	mov	[di + 6],ds		# save new segment
0:	mov	di,bx			# go to next keyword
	or	di,di
	jnz	4b			# check if at end of keyword list
	pop	cx			# restore values for current module
	pop	bx

	mov	di,bx			# go to next module header
	or	di,di
	jnz	1b			# check if at end of module header list
	pop	ds

/*
 * Thats it. We can now set the alternative DOS handler and return without
 * error.
 */

	mov	word ptr [alt_handler],offset dos_handle
	clc
	jmp	9f

6:	pop	cx			# clear stack before returning
	pop	bx
7:	pop	ds
8:	stc				# return with an error
9:	ret



/*
 **************************************************************************
 *
 * Our private DOS function handler. It gets called exactly like any function
 * in the DOS simulator.
 * Input:  AH     -  function code
 *         DS     -  our own data segment
 *         SS:SP  -  our own DOS stack
 *         1. Arg -  callers DS
 *         2. Arg -  callers BP
 *         other registers depend on the function called
 * Output: depends on function called
 * Registers changed: depends on function called
 *
 */
dos_handle:

/*
 * First check for open function. We only accept it if its for the protocol
 * manager device.
 */

	cmp	ah,DOS_FN_FILE_OPEN	# check for open function
	jne	4f
	push	bp
	mov	bp,sp
	push	ax
	push	es
	push	di
	push	si
	xor	ah,ah			# AH is device name indicator
	mov	si,offset devpfx	# set ES:DI to function argument
5:	mov	es,[bp + 4]		# set DS:SI to name of protocol
	mov	di,dx			# manager device
1:	mov	al,es:[di]		# load next character
	cmp	al,0x41
	jb	2f
	cmp	al,0x5A			# convert it to lower case
	ja	2f
	add	al,0x20
2:	cmp	al,[si]			# compare it with device name
	jne	6f
	inc	si
	inc	di			# goto next character
	or	al,al			# check if at end of string
	jnz	1b
3:	pop	si
	pop	di
	pop	es
	pop	ax
	pop	bp
	jnz	9f			# filename not found
	mov	ax,PM_HANDLE		# return device handle
	jmp	8f			# return with carry flag cleared

6:	or	ah,ah			# check if compared to both names:
	jnz	3b			# with and without device name prefix
	inc	ah
	mov	si,offset pmfile	# use device name without prefix
	jmp	5b

/*
 * Now handle close request. We only handle it if its for our protocol manager
 * device. Actually, we dont have to do anything here.
 */

4:	cmp	ah,DOS_FN_FILE_CLOSE	# check for close function
	jne	6f
	cmp	bx,PM_HANDLE		# if not for protman device, let
	jne	9f			# DOS simulator handle the request
	jmp	8f			# otherwise return without error

/*
 * Finally handle IOTCL request. We also handle this request only if its for
 * the protocol manager device
 */

6:	cmp	ah,DOS_FN_FILE_IOCTL	# check for IOCTL function
	jne	9f
	cmp	bx,PM_HANDLE		# if not for protman device, let
	jne	9f			# DOS simulator handle the request
	jmp	ioctl	

/*
 * We handled the function request. In order to return to the DOS simulators
 * exit routine, we have to remove our return address, the arguments and the
 * address of the DOS simulator internal function from the stack. Then we can
 * use the return instruction to get back to the simulator.
 */

7:	add	sp,8
	stc				# return with error
	ret

8:	add	sp,8
	clc				# return without error
9:	ret				# return to DOS simulator


/*
 * Finally handle IOCTL requests. We put this at the end of the routine
 * in order to allow for short jumps to the return labels.
 */

ioctl:	cmp	al,DOS_IOCTL_GETINFO
	jne	1f
	mov	dx,PM_DEVINFO		# get device driver info
	jmp	8b

1:	cmp	al,DOS_IOCTL_RDCHAR
	jne	4f
	cmp	cx,PMREQ_SIZE		# check for correct request header size
	jae	3f
	mov	ax,DOS_ERR_INVDATA	# return with error
	jmp	7b

3:	push	bp
	mov	bp,sp
	push	ax			# since the protocol manager is only
	push	bx			# required to save SI, DI and BP, we
	push	cx			# have to care for all other registers
	push	dx
	push	[bp + 4]		# push address of request block
	push	dx
	push	ds			# push protocol manager data segment
	push	cs			# protocol manager returns with far ret
	call	protman			# call protocol manager
	pop	dx
	pop	cx			# restore registers
	pop	bx
	pop	ax
	pop	bp
	jmp	8b			# return without error

4:	mov	ax,DOS_ERR_NOFN		# all other functions are not supported
	jmp	7b



/*
 **************************************************************************
 *
 * This is the protocol manager function dispatcher. It gets called by
 * a far call either from the DOS handler or directly from a NDIS module
 * in pascal calling style.
 * Input:  1. Arg  -  far pointer to protocol manager request header
 *         2. Arg  -  protocol manager data segment
 * Output: AX      -  error code
 * Registers changed: AX, BX, CX, DX
 *
 */
protman:

	push	bp			# setup standard stack frame
	mov	bp,sp
	push	ds
	push	es
	push	di
	les	di,[bp + 8]		# get pointer to request structure
	mov	ds,[bp + 6]		# get protocol manager data segment

/*
 * Check for correct function number and call appropriate function handler
 */

	mov	bx,es:pmreq_opcode[di]
	cmp	bx,PMREQ_MINFN		# check for function number being in
	jb	8f			# the correct range
	cmp	bx,PMREQ_MAXFN
	ja	8f
	sub	bx,PMREQ_MINFN
	shl	bx,1			# get function handler address
	push	offset 9f		# push return address
	jmp	cs:jmptab[bx]		# call function handler

8:	mov	ax,NDISERR_FUNCTION	# return with invalid function error
9:	mov	es:pmreq_status[di],ax	# set status of function call
	pop	di
	pop	es			# restore registers and return to caller
	pop	ds
	pop	bp
	lret	6			# return with removing the arguments


/*
 * Function handler jump table
 */

jmptab:	.word	pmfunc1
	.word	pmfunc2
	.word	unused
	.word	pmfunc4
	.word	unused
	.word	unused
	.word	unused
	.word	unused
	.word	unused
	.word	pmfunc10



/*
 **************************************************************************
 *
 * Unimplemented functions:
 *
 *  3  -  BindAndStart: only called by application program which doesnt exist
 *                      in the bootrom
 *  5  -  GetProtocolIniPath: we dont have a real PROTOCOL.INI file and
 *                            therefore no valid path
 *  6  -  RegisterProtocolManagerInfo: only used in dynamic mode, which we
 *                                     dont implement
 *  7  -  InitAndRegister: only used with OS/2
 *  8  -  UnbindAndStop: only used in dynamic mode, called by application
 *                       program
 *  9  -  BindStatus: not enabled per NDIS 2.0.1 (assuming BINDSTATUS=NO)
 *
 * Input:  ES:DI  -  pointer to protocol manager request header
 * Output: AX     -  error code
 * Registers changed: AX
 *
 */
unused:

	mov	ax,NDISERR_FUNCTION	# simply return with error
	ret



/*
 **************************************************************************
 *
 * Protocol manager function 1: GetProtocolManagerInfo
 * Input:  ES:DI  -  pointer to protocol manager request header
 * Output: AX     -  error code
 * Registers changed: AX
 *
 */
pmfunc1:

	mov	word ptr es:pmreq_ptr1[di + 0],0	# save pointer to
	mov	ax,[pi_seg]				# PROTOCOL.INI image
	mov	word ptr es:pmreq_ptr1[di + 2],ax	# into request header
	mov	word ptr es:pmreq_word1[di],NDIS_MAJOR_VER + (NDIS_MINOR_VER * 256)
	xor	ax,ax					# return without error
	ret



/*
 **************************************************************************
 *
 * Protocol manager function 2: RegisterModule
 * Input:  ES:DI  -  pointer to protocol manager request header
 * Output: AX     -  error code
 * Registers changed: AX, BX
 *
 */
pmfunc2:

	push	es

/*
 * The only module which we want to know about is the first MAC. So first
 * check if a MAC has already been registered, and that we have indeed a
 * MAC request here. Then simply save the CCT address. We dont need the
 * bindings list address, as a MAC cant have a lower bindings list.
 */

	cmp	dword ptr [mac_cct],0
	mov 	ax,NDISERR_REGISTERED
	jne	9f

	les	bx,es:pmreq_ptr1[di]	# load pointer to CCT
	mov	ax,NDISERR_PARAM
	test	word ptr es:cct_function_flags[bx],MODFUNC_DYN_BIND
	jnz	9f			# dynamic binding not allowed
	cmp	byte ptr es:cct_upper_level[bx],PROT_MAC
	jne	9f
	cmp	byte ptr es:cct_lower_level[bx],PROT_PHYS
	jne	9f
	cmp	byte ptr es:cct_upper_type[bx],TYPE_MAC
	jne	9f

/*
 * Save module ID into CCT and save MAC CCT address
 */

	mov	word ptr es:cct_module_id[bx],MODID_MAC
	mov	word ptr [mac_cct + 0],bx
	mov	word ptr [mac_cct + 2],es
	xor	ax,ax			# return without error
9:	pop	es
	ret



/*
 **************************************************************************
 *
 * Protocol manager function 4: GetProtocolManagerLinkage
 * Input:  ES:DI  -  pointer to protocol manager request header
 * Output: AX     -  error code
 * Registers changed: AX
 *
 */
pmfunc4:

	mov	word ptr es:pmreq_ptr1[di + 0],offset protman
	mov	word ptr es:pmreq_ptr1[di + 2],cs
	mov	word ptr es:pmreq_word1[di],ds
	xor	ax,ax
	ret



/*
 **************************************************************************
 *
 * Protocol manager function 10: RegisterStatus
 * Input:  ES:DI  -  pointer to protocol manager request header
 * Output: AX     -  error code
 * Registers changed: AX
 *
 */
pmfunc10:

	cld
	push	ds
	push	es
	push	si
	push	di
	mov	ax,NDISERR_REGISTERED	# return with error if name found
	lds	si,[mac_cct]		# load MAC CCT pointer
	mov	dx,ds
	or	dx,si			# check if CCT pointer is valid
	jz	8f
	add	si,_I(cct_mod_name)	# let DS:SI point to module name
	les	di,es:pmreq_ptr2[di]	# load module name pointer
	mov	cx,16
	rep	cmpsb			# compare both strings
	je	9f			# names are identical
	mov	dl,es:[di - 1]		# check if last byte is the terminating
	or	dl,[si - 1]		# zero in both strings
	jz	9f
8:	xor	ax,ax			# name not found -> no error
9:	pop	di
	pop	si
	pop	es
	pop	ds
	ret




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

	.end

