	include	defs.asm

;  Copyright, 1988-9, 1990, Russell Nelson

;   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, version 1.
;
;   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.

code	segment word public
	assume	cs:code, ds:code

	public	phd_environ
	org	2ch
phd_environ	dw	?

	public	phd_dioa
	org	80h
phd_dioa	label	byte

	org	100h
start:
	jmp	start_1
	extrn	start_1: near
	even				;put the stack on a word boundary.

;we use our dioa for a stack space.  Very hard usage has shown that only
;  27 bytes were being used, so 128 should be sufficient.
our_stack	label	byte


	extrn	int_no: byte
	public	packet_int_no, is_at, sys_features, flagbyte
packet_int_no	db	?,?,?,?		; interrupt to communicate.
is_at		db	0		; =1 if we're on an AT.
sys_features	db	0		; 2h = MC   40h = 2nd 8259
flagbyte	db	0
	even

functions	label	word
	dw	f_not_implemented	;0
	dw	f_driver_info		;1
	dw	f_access_type		;2
	dw	f_release_type		;3
	dw	f_send_pkt		;4
	dw	f_terminate		;5
	dw	f_get_address		;6
	dw	f_reset_interface	;7
	dw	f_stop			;8
	dw	f_not_implemented	;9
	dw	f_get_parameters	;10
	dw	f_not_implemented	;11
	dw	f_as_send_pkt		;12
	dw	f_drop_pkt		;13
	dw	f_not_implemented	;14
	dw	f_not_implemented	;15
	dw	f_not_implemented	;16
	dw	f_not_implemented	;17
	dw	f_not_implemented	;18
	dw	f_not_implemented	;19
	dw	f_set_rcv_mode		;20
	dw	f_get_rcv_mode		;21
	dw	f_set_multicast_list	;22
	dw	f_get_multicast_list	;23
	dw	f_get_statistics	;24
	dw	f_set_address		;25

	extrn	driver_class: byte
	extrn	driver_type: byte
	extrn	driver_name: byte
	extrn	driver_function: byte
	extrn	parameter_list: byte

	extrn	send_pkt: near
	extrn	as_send_pkt: near
	extrn	drop_pkt: near
	extrn	get_address: near
	extrn	set_address: near
	extrn	terminate: near
	extrn	reset_interface: near
	extrn	xmit: near
	extrn	recv: near
	extrn	recv_exiting: near
	extrn	etopen: near

	extrn	rcv_modes: word		;count of modes followed by mode handles.

	extrn	set_multicast_list: near

linc	macro	n			; inc a 32 bit integer
	local	a
	inc	n			;increment the low word
	jne	a			;go if not overflow
	inc	n+2			;increment the high word
a:
	endm

per_handle	struc
in_use		db	0		;non-zero if this handle is in use.
packet_type	db	MAX_P_LEN dup(0);associated packet type.
packet_type_len	dw	0		;associated packet type length.
receiver	dd	0		;receiver handler.
receiver_sig	db	8 dup(?)	;signature at the receiver handler.
per_handle	ends

handles		per_handle max_handle dup(<>)
end_handles	label	byte

	public	multicast_count, multicast_addrs, multicast_broad
multicast_count	dw	0		;count of stored multicast addresses.
multicast_broad	db	0ffh,0ffh,0ffh,0ffh,0ffh,0ffh	; entry for broadcast
multicast_addrs	db	MAX_MULTICAST*EADDR_LEN dup(?)

have_my_address	db	0		;nonzero if our address has been set.
my_address	db	MAX_ADDR_LEN dup(?)
my_address_len	dw	?

rcv_mode_num	dw	3

free_handle	dw	0		; temp, a handle not in use
found_handle	dw	0		; temp, handle for our packet
receive_ptr	dd	0		; the pkt receive service routine

	public	send_head, send_tail
send_head	dd	0		; head of transmit queue
send_tail	dd	0		; tail of transmit queue

statistics_list	label	dword
packets_in	dw	?,?
packets_out	dw	?,?
bytes_in	dw	?,?
bytes_out	dw	?,?
errors_in	dw	?,?
errors_out	dw	?,?
packets_dropped	dw	?,?		;dropped due to no type handler.

savess		dw	?		;saved during the stack swap.
savesp		dw	?

regs	struc				; stack offsets of incoming regs
_ES	dw	?
_DS	dw	?
_BP	dw	?
_DI	dw	?
_SI	dw	?
_DX	dw	?
_CX	dw	?
_BX	dw	?
_AX	dw	?
_IP	dw	?
_CS	dw	?
_F	dw	?			; flags, Carry flag is bit 0
regs	ends

CY	equ	0001h
EI	equ	0200h


bytes	struc				; stack offsets of incoming regs
	dw	?			; es, ds, bp, di, si are 16 bits
	dw	?
	dw	?
	dw	?
	dw	?
_DL	db	?
_DH	db	?
_CL	db	?
_CH	db	?
_BL	db	?
_BH	db	?
_AL	db	?
_AH	db	?
bytes	ends

	public	our_isr, their_isr
their_isr	dd	0		; original owner of pkt driver int

our_isr:
	jmp	our_isr_0		;the required signature.
	db	'PKT DRVR',0
our_isr_0:
	assume	ds:nothing
	push	ax
	push	bx
	push	cx
	push	dx
	push	si
	push	di
	push	bp
	push	ds
	push	es
	cld
	mov	bx,cs			;set up ds.
	mov	ds,bx
	assume	ds:code
	mov	bp,sp			;we use bp to access the original regs.
	and	_F[bp],not CY		;start by clearing the carry flag.

	test	flagbyte,CALLED_ETOPEN	; have we initialized the card?
	jnz	our_isr_cont		; yes
	push	ax			; save lots of registers
	push	bx
	push	cx
	push	dx
	push	si
	push	di
	push	bp
	push	ds
	push	es

	call	etopen			; init the card

	pop	es			; restore lots of registers
	pop	ds
	pop	bp
	pop	di
	pop	si
	pop	dx
	pop	cx
	pop	bx
	pop	ax
	mov	dh,CANT_RESET		; (actually can't initialize)
	jc	our_isr_error
	or	flagbyte,CALLED_ETOPEN	; remember this fact
 our_isr_cont:

	mov	bl,ah			;jump to the correct function.
	mov	bh,0
	cmp	bx,25			;only twenty five functions right now.
	mov	dh,BAD_COMMAND		;in case we find a bad number.
	ja	our_isr_error
	add	bx,bx			;*2
	call	functions[bx]
	jnc	our_isr_return
our_isr_error:
	mov	_DH[bp],dh
	or	_F[bp],CY		;return their carry flag.
our_isr_return:
	pop	es
	pop	ds
	pop	bp
	pop	di
	pop	si
	pop	dx
	pop	cx
	pop	bx
	pop	ax
	iret

	public	re_enable_interrupts
re_enable_interrupts:
; Possibly re-enable interrupts.  We put this here so that other routines
; don't need to know how we put things on the stack.
	test	_F[bp], EI		; Were interrupts enabled on pkt driver entry?
	je	re_enable_interrupts_1	; No.
	sti				; Yes, re-enable interrupts now.
re_enable_interrupts_1:
	ret


f_not_implemented:
	mov	dh,BAD_COMMAND
	stc
	ret


f_driver_info:
;	As of 1.08, the handle is optional, so we no longer verify it.
;	call	verify_handle
	cmp	_AL[bp],0ffh		; correct calling convention?
	jne	f_driver_info_1		; ne = incorrect, fail
	mov	_BX[bp],majver		;version
	mov	al,driver_class
	mov	_CH[bp],al
	mov	al,driver_type
	cbw
	mov	_DX[bp],ax
	mov	_CL[bp],0		;number zero.
	mov	_DS[bp],ds		; point to our name in their ds:si
	mov	_SI[bp],offset driver_name
	mov	al,driver_function
	mov	_AL[bp],al
	clc
	ret
f_driver_info_1:
	stc
	ret


f_set_rcv_mode:
	call	verify_handle
	mov	cx,_CX[bp]		;Tell them how much room they have.
	cmp	cx,rcv_modes		;do they have this many modes?
	jae	f_set_rcv_mode_1	;no - must be a bad mode for us.
	mov	bx,cx
	add	bx,bx			;we're accessing words, not bytes.
	mov	ax,rcv_modes[bx]+2	;get the handler for this mode.
	or	ax,ax			;do they have one?
	je	f_set_rcv_mode_1	;no - must be a bad mode for us.
	mov	rcv_mode_num,cx		;yes - remember the number and
	call	ax			;  call it.
	clc
	ret
f_set_rcv_mode_1:
	mov	dh,BAD_MODE
	stc
	ret


f_get_rcv_mode:
	call	verify_handle
	mov	ax,rcv_mode_num		;return the current receive mode.
	mov	_AX[bp],ax
	clc
	ret


f_set_multicast_list:
;	mov	cx,_CX[bp]		;Tell them how much room they have.

;verify that they supplied an even number of EADDR's.
	mov	ax,cx
	xor	dx,dx
	mov	bx,EADDR_LEN
	div	bx
	or	dx,dx			;zero remainder?
	jne	f_set_multicast_list_2	;no, we don't have an even number of
					;  addresses.

	cmp	ax,MAX_MULTICAST	;is this too many?
	ja	f_set_multicast_list_3	;yes - return NO_SPACE
f_set_multicast_list_1:
	mov	multicast_count,ax	;remember the number of addresses.
	push	cs
	pop	es
	mov	di,offset multicast_addrs
	push	ds
	mov	ds,_ES[bp]		; get ds:si -> new list.
	mov	si,_DI[bp]
	push	cx
	rep	movsb
	pop	cx
	pop	ds

	mov	si,offset multicast_addrs
	call	set_multicast_list
	ret
f_set_multicast_list_2:
	mov	dh,BAD_ADDRESS
	stc
	ret
f_set_multicast_list_3:
	mov	dh,NO_SPACE
	stc
	ret


f_get_multicast_list:
	mov	_ES[bp],ds		;return what we have remembered.
	mov	_DI[bp],offset multicast_addrs
	mov	ax,EADDR_LEN		;multiply the count by the length.
	mul	multicast_count
	mov	_CX[bp],ax		;because they want total bytes.
	clc
	ret


f_get_statistics:
	call	verify_handle		;just in case.
	mov	_DS[bp],ds
	mov	_SI[bp],offset statistics_list
	clc
	ret


access_type_class:
	mov	dh,NO_CLASS
	stc
	ret

access_type_type:
	mov	dh,NO_TYPE
	stc
	ret

access_type_number:
	mov	dh,NO_NUMBER
	stc
	ret

access_type_bad:
	mov	dh,BAD_TYPE
	stc
	ret

access_type_space:
	mov	dh,NO_SPACE
	stc
	ret

;register caller of pkt TYPE
f_access_type:
	mov	al,driver_class
	cmp	_AL[bp],al		;our class?
	jne	access_type_class	;no.
	cmp	_BX[bp],-1		;generic type?
	je	access_type_2		;yes.
	mov	al,driver_type
	cbw
	cmp	_BX[bp],ax		;our type?
	jne	access_type_type	;no.
access_type_2:
	cmp	_DL[bp],0		;generic number?
	je	access_type_3
	cmp	_DL[bp],1		;our number?
	jne	access_type_number
access_type_3:
	cmp	_CX[bp],MAX_P_LEN	;is the type length too long?
	ja	access_type_bad		;yes - can't be ours.

; now we do two things--look for an open handle, and check the existing
; handles to see if they're replicating a packet type.

	mov	free_handle,0		;remember no free handle yet.
	mov	bx,offset handles
access_type_4:
	cmp	[bx].in_use,0		;is this handle in use?
	je	access_type_5		;no - don't check the type.
	mov	es,_DS[bp]		;get a pointer to their type
	mov	di,_SI[bp]		;  from their ds:si to our es:di
	mov	cx,_CX[bp]		;get the minimum of their length
					;  and our length.  As currently
					;  implemented, only one receiver
					;  gets the packets, so we have to
					;  ensure that the shortest prefix
					;  is unique.
	cmp	cx,[bx].packet_type_len	;Are we less specific than they are?
	jb	access_type_8		;no.
	mov	cx,[bx].packet_type_len	;yes - use their count.
access_type_8:
	lea	si,[bx].packet_type
	or	cx,cx			; pass-all TYPE? (zero TYPE length)
	jne	access_type_7		; ne = no
	mov	bx,offset handles+(max_handle -1)*(size per_handle)
	jmp	short access_type_5	; put pass-all last
access_type_7:
	repe	cmpsb
	jne	short access_type_6	;go look at the next one.
access_type_inuse:
	mov	dh,TYPE_INUSE		;a handle has been assigned for TYPE
	stc				;and we can't assign another
	ret
access_type_5:				;handle is not in use
	cmp	free_handle,0		;found a free handle yet?
	jne	access_type_6		;yes.
	mov	free_handle,bx		;remember a free handle
access_type_6:
	add	bx,(size per_handle)	;go to the next handle.
	cmp	bx,offset end_handles	;examined all handles?
	jb	access_type_4		;no, continue.

	mov	bx,free_handle		;did we find a free handle?
	or	bx,bx
	je	access_type_space	;no - return error.

	mov	[bx].in_use,1		;remember that we're using it.

	mov	ax,_DI[bp]		;remember the receiver type.
	mov	[bx].receiver.offs,ax
	mov	ax,_ES[bp]
	mov	[bx].receiver.segm,ax

	push	ds
	mov	ax,ds
	mov	es,ax
	mov	ds,_DS[bp]		;remember their type.
	mov	si,_SI[bp]
	mov	cx,_CX[bp]
	mov	es:[bx].packet_type_len,cx	; remember the TYPE length
	lea	di,[bx].packet_type
	rep	movsb

	lds	si,es:[bx].receiver	;copy the first 8 bytes
	lea	di,[bx].receiver_sig	; to the receiver signature.
	mov	cx,8/2
	rep	movsw

	pop	ds

	mov	_AX[bp],bx		;return the handle to them.

	clc
	ret


f_release_type:
	call	verify_handle		;mark this handle as being unused.
	mov	[bx].in_use,0
	clc
	ret


f_send_pkt:
;ds:si -> buffer, cx = length
; XXX Should re-enable interrupts here, but some drivers are broken.
; Possibly re-enable interrupts.
;	test _F[bp], EI		; Were interrupts enabled on pkt driver entry?
;	je	f_send_pkt_1	; No.
;	sti			; Yes, re-enable interrupts now.
;f_send_pkt_1:
	push	ds		; set up proper ds for the buffer
	mov	ds,_DS[bp]	; address of buffer from caller's ds.
	assume	ds:nothing, es:nothing
;following two instructions not needed because si and cx haven't been changed.
;	mov	si,_SI[bp]
;	mov	cx,_CX[bp]	; count of bytes in the packet.
	linc	packets_out
	add	bytes_out.offs,cx	;add up the received bytes.
	adc	bytes_out.segm,0

; If -n option take Ethernet encapsulated Novell IPX packets (from BYU's 
; PDSHELL) and change them to be IEEE 802.3 encapsulated.
EPROT_OFF	equ	EADDR_LEN*2
	test	cs:flagbyte,N_OPTION
	jz	f_send_pkt_2
	cmp	ds:[si].EPROT_OFF,3781h ; if not Novell (prot 8137)
	jne	f_send_pkt_2		;  don't tread on it
	push	ax			; get scratch reg
	mov	ax,[si].EPROT_OFF+4	; get len
	xchg	ah,al
	inc	ax			; make even (rounding up)
	and	al,0feh
	xchg	ah,al
	mov	ds:[si].EPROT_OFF,ax	; save in prot field
	pop	ax			; restore old contents
f_send_pkt_2:
	call	send_pkt
	pop	ds
	assume	ds:code
	ret


f_as_send_pkt:
;es:di -> iocb.
	test	driver_function,4	; is this a high-performance driver?
	je	f_as_send_pkt_2		; no.
; Possibly re-enable interrupts.
	test _F[bp], EI			; Were interrupts enabled on pkt driver entry?
	je	f_as_send_pkt_1		; No.
	sti				; Yes, re-enable interrupts now.
f_as_send_pkt_1:
	push	ds			; set up proper ds for the buffer
	lds	si,es:[di].buffer	; ds:si -> buffer
	assume	ds:nothing
	mov	cx,es:[di].len		; cx = length
	linc	packets_out
	add	bytes_out.offs,cx	; add up the received bytes.
	adc	bytes_out.segm,0

;ds:si -> buffer, cx = length, es:di -> iocb.
	call	as_send_pkt
	pop	ds
	assume	ds:code
	ret
f_as_send_pkt_2:
	mov dh,	BAD_COMMAND		; return an error.
	stc
	ret


f_drop_pkt:
; es:di -> iocb.
	test	driver_function,4	; is this a high-performance driver?
	je	f_as_send_pkt_1		; no.
	push	ds			; Preserve ds
	mov	si,offset send_head	; Get head offset
dp_loop:
	mov	ax,ds:[si]		; Get offset
	mov	dx,ds:[si+2]		; Get segment
	mov	bx,ax
	or	bx,dx			; End of list?
	je	dp_endlist		; Yes
	cmp	ax,di			; Offsets equal?
	jne	dp_getnext		; No
	mov	bx,es
	cmp	dx,bx			; Segments equal?
	jne	dp_getnext		; No
	call	drop_pkt		; Pass to driver
	les	di,es:[di].next		; Get next segment:offset
	mov	ds:[si],di		; Set next offset
	mov	ds:[si+2],es		; Set next segment
	pop	ds			; Restore ds
	clc
	ret
dp_getnext:
	mov	ds,dx			; Get next segment
	mov	si,ax			; Get next iocb offset
	lea	si,ds:[si].next		; Get next iocb next ptr offset
	jmp	dp_loop			; Try again
dp_endlist:
	pop	ds			; Restore ds
	mov	dh,BAD_IOCB		; Return error
	stc				; Set carry
	ret


f_terminate:
	call	verify_handle		; must have a handle

f_terminate_1:
	mov	[bx].in_use,0		; mark handle as free
	mov	bx,offset handles	; check that all handles are free
f_terminate_2:
	cmp	[bx].in_use,0		; is this handle free?
	jne	f_terminate_4		; ne = no, so can't exit completely
	add	bx,(size per_handle)	; next handle
	cmp	bx,offset end_handles	; examined all handles?
	jb	f_terminate_2		; b = no, continue examination

	call	terminate		;terminate the hardware.
;
; Now disable interrupts
;
	mov	al,int_no
	or	al,al			;are they using a hardware interrupt?
	je	f_terminate_no_irq	;no.
	call	maskint

;
; Now return the interrupt to their handler.
;
	mov	ah,25h			;get the old interrupt into es:bx
	mov	al,int_no
	add	al,8
	cmp	al,8+8			;is it a slave 8259 interrupt?
	jb	f_terminate_3		;no.
	add	al,70h - (8+8)		;map it to the real interrupt.
f_terminate_3:
	push	ds
	lds	dx,their_recv_isr
	int	21h
	pop	ds

f_terminate_no_irq:
	mov	al,packet_int_no	;release our_isr.
	mov	ah,25h
	push	ds
	lds	dx,their_isr
	int	21h
	pop	ds

;
; Now free our memory
;
	push	cs
	pop	es
	mov	ah,49h
	int	21h
	clc
	ret
f_terminate_4:
	mov	dh, CANT_TERMINATE
	stc
	ret



f_get_address:
	call	verify_handle
;	mov	es,_ES[bp]		; get new one
;	mov	di,_DI[bp]		; get pointer, es:di is ready
	mov	cx,_CX[bp]		;Tell them how much room they have.
	cmp	have_my_address,0	;has our address been set?
	jne	get_address_set		;yes - go report it.
	call	get_address		;no, can we get the address?
	jc	get_address_space	;no - we must not have enough space.
	mov	_CX[bp],cx		;Tell them how long our address is.
	clc
	ret
get_address_set:
	cmp	cx,my_address_len	;is there enough room?
	jb	get_address_space	;no.
	mov	cx,my_address_len	;yes - get our address length.
	mov	_CX[bp],cx		;Tell them how long our address is.
	mov	si,offset my_address	;copy it into their area.
	rep	movsb
	clc
	ret

get_address_space:
	mov	dh,NO_SPACE
	stc
	ret


f_set_address:
	mov	bx,offset handles
	mov	cl,0			;number of handles in use.
f_set_address_1:
	add	cl,[bx].in_use		;is this handle in use?
	add	bx,(size per_handle)	;go to the next handle.
	cmp	bx,offset end_handles
	jb	f_set_address_1

	cmp	cl,1			;more than one handle in use?
	ja	f_set_address_inuse	;yes - we can't set the address

	mov	ds,_ES[bp]		; set new one
	assume	ds:nothing
	mov	si,_DI[bp]		; set pointer, ds:si is ready
;	mov	cx,_CX[bp]		;Tell them how much address is being set.
	call	set_address
;set_address restores ds.
	jc	f_set_address_exit	;Did it work?
	mov	_CX[bp],cx		;yes - return our address length.

	cmp	cx,MAX_ADDR_LEN		;is it too long for us to remember?
	ja	f_set_address_too_long	;yes, return a too-long error.

	mov	ds,_ES[bp]		; set new one
	mov	si,_DI[bp]		; set pointer, ds:si is ready
	mov	ax,cs
	mov	es,ax
	mov	my_address_len,cx	;remember how long our address is.
	mov	di,offset my_address
	rep	movsb
	mov	have_my_address,1
	mov	ds,ax			;restoer ds.
	assume	ds:code
	clc
	ret
f_set_address_inuse:
	mov	dh,CANT_SET
	stc
	ret
f_set_address_too_long:
	mov	dh,NO_SPACE
	stc
f_set_address_exit:
	ret


f_reset_interface:
	call	verify_handle
	call	reset_interface
	clc
	ret


; Stop the packet driver doing upcalls. Also a following terminate will
; always succed (no in use handles any longer).
f_stop:
	mov	bx,offset handles
f_stop_2:
	mov	[bx].in_use,0
	add	bx,(size per_handle)	; next handle
	cmp	bx,offset end_handles
	jb	f_stop_2
	clc
	ret


f_get_parameters:
;strictly speaking, this function only works for high-performance drivers.
	test	driver_function,4	;is this a high-performance driver?
	jne	f_get_parameters_1	;yes.
	mov	dh,BAD_COMMAND		;no - return an error.
	stc
	ret
f_get_parameters_1:
	mov	_ES[bp],cs
	mov	_DI[bp],offset parameter_list
	clc
	ret


verify_handle:
;Ensure that their handle is real.  If it isn't, we pop off our return
;address, and return to *their* return address with cy set.
	mov	bx,_BX[bp]		;get the handle they gave us
	cmp	bx,offset handles
	jb	verify_handle_bad	;no - must be bad.
	cmp	bx,offset end_handles
	jae	verify_handle_bad	;no - must be bad.
	cmp	[bx].in_use,0		;if it's not in use, it's bad.
	je	verify_handle_bad
	ret
verify_handle_bad:
	mov	dh,BAD_HANDLE
	add	sp,2			;pop off our return address.
	stc
	ret


	public	set_recv_isr
set_recv_isr:
	mov	ah,35h			;get the old interrupt into es:bx
	mov	al,int_no		; board's interrupt vector
	or	al,al
	je	set_isr_no_irq
	add	al,8
	cmp	al,8+8			;is it a slave 8259 interrupt?
	jb	set_recv_isr_1		;no.
	add	al,70h - 8 - 8		;map it to the real interrupt.
set_recv_isr_1:
	int	21h
	mov	their_recv_isr.offs,bx	;remember the old seg:off.
	mov	their_recv_isr.segm,es

	mov	ah,25h			;now set our recv interrupt.
	mov	dx,offset recv_isr
	int	21h

	mov	al,int_no		; Now enable interrupts
	call	unmaskint

set_isr_no_irq:
	ret

	public	count_in_err
count_in_err:
	assume	ds:nothing
	linc	errors_in
	ret

	public	count_out_err
count_out_err:
	assume	ds:nothing
	linc	errors_out
	ret

their_recv_isr	dd	0		; original owner of board int

recv_isr:
  if 0
	push	ax
	push	ds
	mov	ax,cs			;ds = cs.
	mov	ds,ax
	assume	ds:code

	mov	savesp,sp
	mov	savess,ss

	mov	ss,ax
	mov	sp,offset our_stack
	cld
	sti

	push	bx
	push	cx
	push	dx
	push	si
	push	di
	push	bp
	push	es

	call	recv

	cli				;interrupts *must* be off between
					;here and the stack restore, because
					;if we have one of our interrupts
					;pending, we would trash our stack.
;
; The following code is ruthlessly stolen from Phil Karn's NET package.
;
	test	sys_features,40h ; 2nd 8259 installed?
	jz	recv_isr_3	; Only one 8259, so skip this stuff
	mov	al,0bh		; read in-service register from
	out	0a0h,al		; secondary 8259
	nop			; settling delay
	nop
	nop
	in	al,0a0h		; get it
	or	al,al		; Any bits set?
	jz	recv_isr_3	; nope, not a secondary interrupt
	mov	al,20h		; Get EOI instruction
	out	0a0h,al		; Secondary 8259 (PC/AT only)
recv_isr_3:
	mov	al,20h			;acknowledge the interrupt.
	out	20h,al

	pop	es
	pop	bp
	pop	di
	pop	si
	pop	dx
	pop	cx
	pop	bx

	mov	ss,savess
	mov	sp,savesp

	call	recv_exiting		;this routine can enable interrupts.

	pop	ds
	pop	ax
  else
; In order to achieve back-to-back packet transmissions, we handle the
; latency-critical portion of transmit interrupts first.  The xmit
; interrupt routine should only start the next transmission, but do
; no other work.  It may only touch ax and dx (the only register necessary
; for doing "out" instructions) unless it first pushes any other registers
; itself.
	push	ax
	push	dx
	call	xmit

; Now switch stacks, push remaining registers, and do remaining interrupt work.
	push	ds
	mov	ax,cs			;ds = cs.
	mov	ds,ax
	assume	ds:code

	mov	savesp,sp
	mov	savess,ss

	mov	ss,ax
	mov	sp,offset our_stack
	cld

	push	bx
	push	cx
	push	si
	push	di
	push	bp
	push	es

; Chips & Technologies 8259 clone chip seems to be very broken.  If you
; send it a Non Specific EOI command, it clears all In Service Register
; bits instead of just the one with the highest priority (as the Intel
; chip does and clones should do).  This bug causes our interrupt
; routine to be reentered if: 1. we reenable processor interrupts;
; 2. we reenable device interrupts; 3. a timer or other higher priority
; device interrupt now comes in; 4. the new interrupting device uses
; a Non Specific EOI; 5. our device interrupts again.  Because of
; this bug, we now completely mask our interrupts around the call
; to "recv", the real device interrupt handler.  This allows us
; to send an EOI instruction to the 8259 early, before we actually
; reenable device interrupts.  Since the interrupt is masked, we
; are still guaranteed not to get another interrupt from our device
; until the interrupt handler returns.  This has another benefit:
; we now no longer prevent other devices from interrupting while our
; interrupt handler is running.  This is especially useful if we have
; other (multiple) packet drivers trying to do low-latency transmits.
	mov	al,int_no	; Disable further device interrupts
	call	maskint

;
; The following code is ruthlessly stolen from Phil Karn's NET package.
;
	test	sys_features,TWO_8259 ; 2nd 8259 installed?
	jz	recv_isr_3	; Only one 8259, so skip this stuff
	mov	al,0bh		; read in-service register from
	out	0a0h,al		; secondary 8259
	nop			; settling delay
	nop
	nop
	in	al,0a0h		; get it
	or	al,al		; Any bits set?
	jz	recv_isr_3	; nope, not a secondary interrupt
	mov	al,20h		; Get EOI instruction
	out	0a0h,al		; Secondary 8259 (PC/AT only)
recv_isr_3:
	mov	al,20h			;acknowledge the interrupt.
	out	20h,al

;	sti				; Interrupts are now completely safe
	call	recv

	cli				;interrupts *must* be off between
					;here and the stack restore, because
					;if we have one of our interrupts
					;pending, we would trash our stack.
	mov	al,int_no	; Now reenable device interrupts
	call	unmaskint

	pop	es
	pop	bp
	pop	di
	pop	si
	pop	cx
	pop	bx

	mov	ss,savess
	mov	sp,savesp

	call	recv_exiting		;this routine can enable interrupts.
; DDP - This is a BIG mistake.  This routine SHOULD NOT enable interrupts.
;	doing so can cause interrupt recursion and blow your stack.
;	Processor interrupts SHOULD NOT be enabled after enabling device
;	interrupts until after the "iret".  You will lose atleast 12 bytes
;	on the stack for each recursion.

	pop	ds
	assume	ds:nothing
	pop	dx
	pop	ax
  endif
	iret


	public	maskint
maskint:
	or	al,al			;are they using a hardware interrupt?
	je	maskint_1		;no, don't mask off the timer!

	assume	ds:code
	mov	dx,21h			;assume the master 8259.
	cmp	al,8			;using the slave 8259 on an AT?
	jb	mask_not_irq2
	mov	dx,0a1h			;go disable it on slave 8259
	sub	al,8
mask_not_irq2:
	mov	cl,al

	in	al,dx			;disable them on the correct 8259.
	mov	ah,1			;set the bit.
	shl	ah,cl
	or	al,ah
	out	dx,al
maskint_1:
	ret


unmaskint:
	assume	ds:code
	mov	dx,21h			;assume the master 8259.
	mov	cl,al
	cmp	cl,8			;using the slave 8259 on an AT?
	jb	unmask_not_irq2		;no
	in	al,dx			;get master mask
	and	al,not (1 shl 2)	; and clear slave cascade bit in mask
	out	dx,al			;set new master mask (enable slave int)
	mov	dx,0a1h			;go enable int on slave 8259
	sub	cl,8
unmask_not_irq2:

	in	al,dx			;enable interrupts on the correct 8259.
	mov	ah,1			;clear the bit.
	shl	ah,cl
	not	ah
	and	al,ah
	out	dx,al

	ret


	public	recv_find
recv_find:
;called when we want to determine what to do with a received packet.
;enter with cx = packet length, es:di -> packet type.
;exit with es:di = 0 if the packet is not desired, or es:di -> packet buffer
;  to be filled by the driver.
	assume	ds:code, es:nothing
	push	cx

; If -n option take IEEE 802.3 encapsulated packets that could be Novell IPX 
; and make them Ethernet encapsulated Novell IPX packets (for PDSHELL).
	test	flagbyte,N_OPTION
	jz	not_n_op
	push	ax			; get scratch reg
	mov	ax,es:[di]		; get length word
	xchg	ah,al
	cmp	ax,GIANT		; if > GIANT
	ja	recv_find_0		;  then it is a protocol
	cmp	es:2[di],0ffffh 	; if next word not ffff
	jne	recv_find_0		;  then not Novell
	mov	es:[di],3781h		; make Novell protocol (8137)
recv_find_0:
	pop	ax			; restore old contents
not_n_op:

	mov	bx,offset handles
recv_find_1:
	cmp	[bx].in_use,0		;is this handle in use?
	je	recv_find_2		;no - don't check the type.
	mov	ax,[bx].receiver.offs	;do they have a receiver?
	or	ax,[bx].receiver.segm
	je	recv_find_2		;no - they're not serious about it.
	mov	cx,[bx].packet_type_len	;compare the packets.
	lea	si,[bx].packet_type
	jcxz	recv_find_3		;if cx is zero, they want them all.
	push	di
	repe	cmpsb
	pop	di
	je	recv_find_3		;we've got it!
recv_find_2:
	add	bx,(size per_handle)	;go to the next handle.
	cmp	bx,offset end_handles
	jb	recv_find_1

	linc	packets_dropped

	pop	cx			;we didn't find it -- discard it.
recv_find_5:
	xor	di,di			;"return" a null pointer.
	mov	es,di
	ret
recv_find_3:
	pop	cx			; the packet_length

	linc	packets_in
	add	bytes_in.offs,cx	;add up the received bytes.
	adc	bytes_in.segm,0

	les	di,[bx].receiver	;remember the receiver upcall.
	mov	receive_ptr.offs,di
	mov	receive_ptr.segm,es

	test	flagbyte,W_OPTION	;did they select the Windows option?
	je	recv_find_6		;no, don't check for the upcall.

; does the receiver signature match whats currently in memory?  if not,
; jump to fake return
	push	si
	push	cx
	lea	si,[bx].receiver_sig
	mov	cx,8/2
	repe	cmpsw
	pop	cx
	pop	si
	jne	recv_find_5
recv_find_6:

	mov	found_handle,bx		;remember what our handle was.
	mov	ax,0			;allocate request.
	stc				;with stc, flags must be an odd number
	push	ax			; save a number that cant be flags
	pushf				;save flags incase iret used.
	call	receive_ptr		;ask the client for a buffer.
	; on return, flags should be at top of stack. if an IRET has nbeen used,
	; then 0 will be at the top of the stack
	pop	bx
	cmp	bx,0
	je	recv_find_4		;0 is at top of stack
	add	sp,2
recv_find_4:
	ret


	public	recv_copy
recv_copy:
;called after we have copied the packet into the buffer.
;enter with ds:si ->the packet, cx = length of the packet.
;preserve bx.
	assume	ds:nothing, es:nothing
	push	bx
	mov	bx,found_handle
	mov	ax,1			;store request.
	clc				;with clc, flags must be an even number
	push	ax			; save a number that cant be flags
	pushf				;save flags incase iret used.
	call	receive_ptr		;ask the client for a buffer.
	pop	bx
	cmp	bx,1			;if this is a 1, IRET was used.
	je	recv_copy_1
	pop	bx
recv_copy_1:
	pop	bx
	ret

	public	send_queue
send_queue:
; Queue an iocb.
; Enter with es:di -> iocb, interrupts disabled.
; Destroys ds:si.
	assume	ds:nothing, es:nothing
	mov	es:[di].next.offs,0	; Zero next offset
	mov	es:[di].next.segm,0	; Zero next segment
	mov	si,send_head.offs	; Queue empty?
	or	si,send_head.segm
	jnz	sq_notempty		; No
	mov	send_head.offs,di	; Set head offset
	mov	send_head.segm,es	; Set head segment
	jmp	sq_settail
sq_notempty:				; Queue is not empty
	lds	si,send_tail		; Get tail segment:offset
	mov	ds:[si].next.offs,di	; Set next offset
	mov	ds:[si].next.segm,es	; Set next segment
sq_settail:
	mov	send_tail.offs,di	; Set tail offset
	mov	send_tail.segm,es	; Set tail segment
	ret


	public	send_dequeue
send_dequeue:
; Dequeue an iocb and possibly call its upcall.
; Enter with device or processor interrupts disabled, ah = return code.
; Exits with es:di -> iocb; destroys ds:si, ax, bx, cx, dx, bp.
	assume	ds:nothing, es:nothing
	les	di,send_head		; Get head segment:offset
	lds	si,es:[di].next		; Get next segment:offset
	mov	send_head.offs, si	; Set head offset
	mov	send_head.segm, ds	; Set head segment
	or	es:flags[di], DONE	; Mark done
	mov	es:ret_code[di], ah	; Set retcode
	test	es:[di].flags,CALLME	; Does he want an upcall?
	je	send_dequeue_1		; No.
	push	es			; Push iocb segment
	push	di			;  and offset
	clc				; Clear carry.
	mov	ax,1			; Push a number that cant be flags.
	push	ax
	pushf				; Save flags in case iret used.
	call	es:[di].upcall		; Call the client.
	pop	ax			; Pop first word.
	cmp	ax,1			; If this is a 1, IRET was used.
	je	send_dequeue_2		; Far return used.
	add	sp,2			; Pop flags.
send_dequeue_2:
	pop	di			; Pop iocb segment
	pop	es			;  and offset
send_dequeue_1:
	ret


code	ends

	end	start
