;  isx_2_3_5.src
; Wing Poon, Deon Roelofse, Chris Waters . V2.3.5 . 1/9/01 . (C) Scenix, Inc.
; SX Ethernet TCP/IP Stack. Protocols implemented: ARP, DHCP, IP/ICMP, UDP,
; 2TCP, HTTP, SMTP

; ******************************************************************************
; NOTES:
;	1. Will work only on SX48/52 Production Release silicon!
;	2. If using SX-Key, you need SXKEY52.EXE V1.19 or greater. Pls go to
;	   the "DEMO DEFINES" section for important information!
;	3. If using SX-ISD, you need SASM.EXE V1.46 and SXIDE.EXE V1.07.03 or
;	   greater. Pls go to the "DEMO DEFINES" section for important
;	   information!
;	4. The schematics for the board that runs this code is available, pls
;	   refer to the CD-ROM or our website: http://www.scenix.com
;	5. There is a Java application program available to demonstrate the
;	   use of the UDP protocol to allow the user to control the SX, pls
;	   refer to the CD-ROM. You'll need the Microsoft Java Virtual Machine
;	   Build 3240, or greater (installed by Default with IE5).
;	6. Refer to the User's Guide for instructions on setting up your PC to
;	   communicate with the Scenix Ethernet Demo Board.
;	7. Refer to Scenix Application Note #37, and the Scenix SX-Stack User's
;	   Manual, for information on how to customize this code
; ******************************************************************************

; This program implements an embedded web-server and a send email client. It is
; written to run on the Scenix Ethernet Demo Board.
;
; The server will respond to HTTP requests and serves up the specified web
; resource stored on an external serial EEPROM. The IP address of the webserver
; is http://10.1.1.20
;
; The email client is capable of sending simple emails to a predefined email
; address. In the demo, an email is sent to "joe@demo.sx" whenever button SW2
; is pressed.
;
; There is a separate demo (requires the SX52 microcontroller to be re-
; programmed, using a third-party programming tool) that can be enabled to
; demonstrate support for DHCP. For this demo, the webserver (HTTP) will be
; disabled and only the email client will remain operational.
;
; The SX can also be controlled by the user, through a standard web broswer
; form.
;
; Finally, the SX can be pinged ("ping 10.1.1.20", or "ping <IP_address>"
; supplied by the DHCP server) using the standard PC/Unix ping utiility.


; ********************
; *** DEMO DEFINES ***
; ********************

SXKEY	= 0	; set to "1" if, and only  if, using Parallax SX-Key tool
		; ... otherwise set to "0"
SASM	= 1	; set to "1" if, and only if, using Advance Transdata or Nohau tool
		; ... otherwise set to "0"

; choose Demo1 or Demo2 by setting DEMO to "1" or "2" in the section below:
;	Demo1 - "Webserver and email client" (kit default when shipped)
;	Demo2 - "Email client configured by DHCP (no webserver)"
DEMO	= 1	; uncomment for: Webserver and Email client
;DEMO	= 2	; uncoment for: Email client configured by DHCP (no webserver)

; fill in your SMTP server's IP address here (default 10.1.1.1) (both demos):
SMTP_SERVER_IP3	= 10
SMTP_SERVER_IP2	= 1
SMTP_SERVER_IP1	= 1
SMTP_SERVER_IP0	= 10

; locate the following functions in the code and customize them if you wish:
; _senderDomainName	(default "sx")
; _mailFrom		(default "sx")
; _mailTo		(default "joe@demo.sx")
; _mailData		(default "Button SW2 pressed")

; if you need to change the demo board's (webserver) IP address (default 10.1.1.20):
SX_IP_ADDR3	= 192	; SX's static IP address (if DHCP Disabled, i.e. Demo1)
SX_IP_ADDR2	= 168	;  "
SX_IP_ADDR1	= 1	;  "
SX_IP_ADDR0	= 105	;  "

; if you need to change the demo board's MAC address (default 00-00-00-00-00-01):
SX_ETH_ADDR0	= 0	; SX's Ethernet Phy MAC Address
SX_ETH_ADDR1	= 3	;  "
SX_ETH_ADDR2	= 0	;  "
SX_ETH_ADDR3	= 0	;  "
SX_ETH_ADDR4	= 0	;  "
SX_ETH_ADDR5	= 2	;  "


; INCLUDE "SX52.inc"
; SX52.inc

;*********************************************************************************
; SX48BD/52BD Mode addresses
; *On SX48BD/52BD, most registers addressed via mode are read and write, with the
; exception of CMP and WKPND which do an exchange with W.
;*********************************************************************************
; Timer (read) addresses
TCPL_R		=	$00	; Read Timer Capture register low byte
TCPH_R		=	$01	; Read Timer Capture register high byte
TR2CML_R	=	$02	; Read Timer R2 low byte
TR2CMH_R	=	$03	; Read Timer R2 high byte
TR1CML_R	=	$04	; Read Timer R1 low byte
TR1CMH_R	=	$05	; Read Timer R1 high byte
TCNTB_R		=	$06	; Read Timer control register B
TCNTA_R		=	$07	; Read Timer control register A

; Exchange addresses
CMP		=	$08	; Exchange Comparator enable/status register with W
WKPND		=	$09	; Exchange MIWU/RB Interrupts pending with W

; Port setup (read) addresses
WKED_R		=	$0A	; Read MIWU/RB Interrupt edge setup, 0 = falling, 1 = rising
WKEN_R		=	$0B	; Read MIWU/RB Interrupt edge setup, 0 = enabled, 1 = disabled
ST_R		=	$0C	; Read Port Schmitt Trigger setup, 0 = enabled, 1 = disabled
LVL_R		=	$0D	; Read Port Level setup, 0 = CMOS, 1 = TTL
PLP_R		=	$0E	; Read Port Pull-up setup, 0 = enabled, 1 = disabled
DIR_R		=	$0F	; Read Port Direction

; Timer (write) addresses
TR2CML_W	=	$12	; Write Timer R2 low byte
TR2CMH_W	=	$13	; Write Timer R2 high byte
TR1CML_W	=	$14	; Write Timer R1 low byte
TR1CMH_W	=	$15	; Write Timer R1 high byte
TCNTB_W		=	$16	; Write Timer control register B
TCNTA_W		=	$17	; Write Timer control register A

; Port setup (write) addresses
WKED_W		=	$1A	; Write MIWU/RB Interrupt edge setup, 0 = falling, 1 = rising
WKEN_W		=	$1B	; Write MIWU/RB Interrupt edge setup, 0 = enabled, 1 = disabled
ST_W		=	$1C	; Write Port Schmitt Trigger setup, 0 = enabled, 1 = disabled
LVL_W		=	$1D	; Write Port Level setup, 0 = CMOS, 1 = TTL
PLP_W		=	$1E	; Write Port Pull-up setup, 0 = enabled, 1 = disabled
DIR_W		=	$1F	; Write Port Direction

;*********************************************************************************
; Setup and enable RTCC interrupt, WREG register, RTCC/WDT prescaler
;*********************************************************************************

RTCC_ON		=	%10000000	; Enables RTCC at address $01 (RTW hi)
					; WREG at address $01 (RTW lo) by default
RTCC_ID		=	%01000000	; Disables RTCC edge interrupt (RTE_IE hi)
					; RTCC edge interrupt (RTE_IE lo) enabled by default
RTCC_INC_EXT	=	%00100000	; Sets RTCC increment on RTCC pin transition (RTS hi)
					; RTCC increment on internal instruction (RTS lo) is defalut
RTCC_FE		=	%00010000	; Sets RTCC to increment on falling edge (RTE_ES hi)
					; RTCC to increment on rising edge (RTE_ES lo) is default
RTCC_PS_OFF	=	%00001000	; Assigns prescaler to Watchdog (PSA hi)
PS_000		=	%00000000	; RTCC = 1:2, WDT = 1:1
PS_001		=	%00000001	; RTCC = 1:4, WDT = 1:2
PS_010		=	%00000010	; RTCC = 1:8, WDT = 1:4
PS_011		=	%00000011	; RTCC = 1:16, WDT = 1:8
PS_100		=	%00000100	; RTCC = 1:32, WDT = 1:16
PS_101		=	%00000101	; RTCC = 1:64, WDT = 1:32
PS_110		=	%00000110	; RTCC = 1:128, WDT = 1:64
PS_111		=	%00000111	; RTCC = 1:256, WDT = 1:128


; ***************************
; *** CONDITIONAL DEFINES ***
; ***************************

	IF	DEMO = 1	; webserver and email client IP address will be 10.1.1.20
DHCP	= 0
HTTP	= 1
SMTP	= 1
	ENDIF

	IF	DEMO = 2	; email client IP address will be as assigned by remote DHCP server
DHCP	= 1
HTTP	= 0
SMTP	= 1
	ENDIF


; **************
; *** DEVICE ***
; **************

	IF	SXKEY
; Parallax -- if, and only if, using Parallax's SX-Key
	DEVICE	OSCHS2, DRT60MS
	FREQ	48000000	; have to Debug at freq != resonant freq
	ENDIF

	IF	SASM
; SASM -- if, and only if, using Advance Transdata's SX-ISD
	DEVICE SX52BD, OSCHS2, WDRT60
	irc_cal		IRC_SLOW
	freq		50_000_000
	ENDIF

	RESET	Main
	ID	'isx_235'


; *****************
; *** VARIABLES ***
; *****************

; *** Global ***
GLOBAL_ORG = $0A

flags		EQU GLOBAL_ORG+0	; various flags used by TCP/IP stack
flags2		EQU GLOBAL_ORG+1	; various flags used by TCP/IP stack
flags3		EQU GLOBAL_ORG+2	; various flags used by TCP/IP stack
globTemp1	EQU GLOBAL_ORG+3	; not preserved across any function
globTemp2	EQU GLOBAL_ORG+4	; not preserved across anyfunction
globTemp3	EQU GLOBAL_ORG+5	; preserved across some functions

; *** Bank 0 ***
; (Don't use this bank - Difficulties with addressing Data)
	ORG	$00

; *** Bank 1 ***
	ORG	$10

NIC_BANK	= $

nicIOAddr		DS 1	; points to currently addressed register on NIC
nicNextPktPtr		DS 1	; points to next packet in NIC's rx queue
nicCurrPktPtr		DS 1	; points to current packet in NIC's rx queue
nicRemoteEth0		DS 1	; ethernet addr used for outgoing packet, overwritten by incoming packet
nicRemoteEth1		DS 1	;  "
nicRemoteEth2		DS 1	;  "
nicRemoteEth3		DS 1	;  "
nicRemoteEth4		DS 1	;  "
nicRemoteEth5		DS 1	;  "
nicCopySrcMSB		DS 1	; used by NICBufferCopy()
nicCopySrcLSB		DS 1	;  "
nicCopyDestMSB		DS 1	;  "
nicCopyDestLSB		DS 1	;  "
nicCopyLenMSB		DS 1	;  "
nicCopyLenLSB		DS 1	;  "
nicCopyTemp		DS 1	;  "

; *** Bank 2 ***
	ORG	$20

IP_BANK		= $	; make sure IP_BANK[7] = NIC_BANK[7]

remoteIP3		DS 1	; IP addr used for outgoing packet, overwritten by incoming packet
remoteIP2		DS 1	;  "
remoteIP1		DS 1	;  "
remoteIP0		DS 1	;  "
myIP3			DS 1	; filter value for incoming IP packets, also used in outgoing packet
myIP2			DS 1	;  "
myIP1			DS 1	;  "
myIP0			DS 1	;  "
ipCheckSumMSB		DS 1	; IP <header_checksum>
ipCheckSumLSB		DS 1	;  "
ipLengthMSB		DS 1	; IP <length>
ipLengthLSB		DS 1	;  "
ipProtocol		DS 1	; IP <protocol>
ipIdentMSB		DS 1	; IP <identifier>, incremented each outgoing packet
ipIdentLSB		DS 1	;  "
counter1		DS 1	; general purpose counter variable

; *** Bank 3 ***
	ORG	$30

TCB1_BANK	= $	; make sure TCB1_BANK[7] = NIC_BANK[7]

; TCB1 is bound to tcp connection1 indicated by flags2.TCP_SOCK cleared
; The ordering of these variables is significant. It is the same as the TCP
; header. This simpifies the send-packet code
tcb1LocalPortMSB	DS 1	; source port - tcp conn1
tcb1LocalPortLSB	DS 1	;  "
tcb1RemotePortMSB	DS 1	; destination port - tcp conn1
tcb1RemotePortLSB	DS 1	;  "
tcb1SndUna4		DS 1	; SND.UNA: oldest unacknowledged byte - tcp conn1
tcb1SndUna3		DS 1	;  "
tcb1SndUna2		DS 1	;  "
tcb1SndUna1		DS 1	;  "
tcb1RcvNxt4		DS 1	; RCV.NXT: next byte to receive - tcp conn1
tcb1RcvNxt3		DS 1	;  "
tcb1RcvNxt2		DS 1	;  "
tcb1RcvNxt1		DS 1	;  "
tcb1Offset		DS 1	; length of the TCP options
tcb1Flags		DS 1	; flags field
tcb1SendWinMSB		DS 1	; send window
tcb1SendWinLSB		DS 1	;  "

TCB1_END	= $

; *** Bank 4 ***
	ORG	$40

TCB2_BANK	= $	; make sure TCB2_BANK[7] = NIC_BANK[7]

; TCB2 is bound to tcp connection2 indicated by flags2.TCP_SOCK set
; The ordering of these variables is significant. It is the same as the TCP
; header. This simpifies the send-packet code
tcb2LocalPortMSB	DS 1	; source port - tcp conn2
tcb2LocalPortLSB	DS 1	;  "
tcb2RemotePortMSB	DS 1	; destination port - tcp conn2
tcb2RemotePortLSB	DS 1	;  "
tcb2SndUna4		DS 1	; SND.UNA: oldest unacknowledged byte - tcp conn2
tcb2SndUna3		DS 1	;  "
tcb2SndUna2		DS 1	;  "
tcb2SndUna1		DS 1	;  "
tcb2RcvNxt4		DS 1	; RCV.NXT: next byte to receive - tcp conn2
tcb2RcvNxt3		DS 1	;  "
tcb2RcvNxt2		DS 1	;  "
tcb2RcvNxt1		DS 1	;  "
tcb2Offset		DS 1	; length of the TCP options
tcb2Flags		DS 1	; flags field
tcb2SendWinMSB		DS 1	; send window
tcb2SendWinLSB		DS 1	;  "

TCB2_END	= $

; *** Bank 5 ***
	ORG	$50

TCP_BANK = $	; make sure TCP_BANK[7] = NIC_BANK[7]

tcp1State		DS 1	; tcp connection1 state-machine state
tcp2State		DS 1	; tcp connection2 state-machine state
tcpTmpSeq4		DS 1	; TMP.SEQ. 1=LSB, 4=MSB
tcpTmpSeq3		DS 1	; temporary information from the received packet
tcpTmpSeq2		DS 1
tcpTmpSeq1		DS 1
tcp1UnAckMSB		DS 1	; number of unacknowledged bytes for tcp connection1
tcp1UnAckLSB		DS 1	;  "
tcp2UnAckMSB		DS 1	; number of unacknowledged bytes for tcp connection2
tcp2UnAckLSB		DS 1	;  "
tcpRxFlags		DS 1	; copy of the received flags field
tcpCheckSumMSB		DS 1
tcpCheckSumLSB		DS 1
tcpLengthMSB		DS 1
tcpLengthLSB		DS 1

tcpTmpMSB		= tcpTmpSeq4
tcpTmpLSB		= tcpTmpSeq3
tcpAppTxBytesMSB	= tcp1UnAckMSB	; number of bytes app wants to transmit
tcpAppTxBytesLSB	= tcp1UnAckLSB	;  "
tcpAppTxBytesMSB	= tcp2UnAckMSB	; number of bytes app wants to transmit
tcpAppTxBytesLSB	= tcp2UnAckLSB	;  "
tcpAppRxBytesMSB	= tcpLengthMSB	; number of bytes app will be receiving
tcpAppRxBytesLSB	= tcpLengthLSB	;  "

; *** Bank 6 ***
	ORG	$60

ARP_BANK	= $	; make sure ARP_BANK[7] = NIC_BANK[7]

host1IP3		DS 1	; remote host1 IP address
host1IP2		DS 1	;  "
host1IP1		DS 1	;  "
host1IP0		DS 1	;  "
host1Eth0		DS 1	; remote host1 Ethernet address
host1Eth1		DS 1	;  "
host1Eth2		DS 1	;  "
host1Eth3		DS 1	;  "
host1Eth4		DS 1	;  "
host1Eth5		DS 1	;  "
stPktTxBufStart		DS 1	; start address of stalled packet in NIC tx buffer

TCPPORT_BANK	= $

; Incoming tcp packet ports
tcpRemotePortMSB	DS 1
tcpRemotePortLSB	DS 1
tcpLocalPortMSB		DS 1
tcpLocalPortLSB		DS 1

; *** Bank 7 ***
	ORG	$70

TIMER_BANK	= $	; make sure TIMER_BANK[7] = NIC_BANK[7]

baseTimer		DS 1	; lowest/common cog in timer chain
arpTimerMSB		DS 1	; ARP-timer count
arpTimerLSB		DS 1	;  "
tcp1TimerMSB		DS 1	; tcp1 re-transmission timer count
tcp1TimerLSB		DS 1	;  "
tcp2TimerMSB		DS 1	; tcp2 re-transmission timer count
tcp2TimerLSB		DS 1	;  "
conn1TimerMSB		DS 1	; tcp1 timeout timer count
conn1TimerLSB		DS 1	;  "
conn2TimerMSB		DS 1	; tcp2 timeout timer count
conn2TimerLSB		DS 1	;  "

	IF	HTTP
HTTP_BANK	= $	; make sure HTTP_BANK[7] = NIC_BANK[7]

httpParseState		DS 1	; state of the HTTP header parser
httpURIHash		DS 1	; hash of the current URI
httpParseState2		DS 1	; state of the HTTP message parser
	ENDIF

	IF	SMTP
SMTP_BANK	= $

smtpState		DS 1	; state of the SMTP state machine
smtpStrPointer		DS 1	; SMTP string pointer
	ENDIF

; *** Bank 8 ***
	ORG	$80

MISC_BANK	= $

; Watch out for this when moving Databanks! This is the only place where more
; than 1 byte is reserved for Data and it's not so obvious!
bcd3			DS 3	; buffer for binary-to-ascii conversion
pageCount		DS 1	; num times resource.htm page has been accessed

ADC_BANK	= $	; must be same bank as bcd3

adc			DS 1	; averaged ADC value
adcAcc			DS 1
adcCount		DS 1
adcMSB			DS 1	; for averaging 256 samples
adcLSB			DS 1	;  "
adcSampleCount		DS 1	; count number of averaged samples

EEPROM_BANK	= $	; make sure EEPROM_BANK[7] = NIC_BANK[7]

e2AddrMSB		DS 1	; address in EEPROM to start reading from
e2AddrLSB		DS 1	;  "
e2FileLenMSB		DS 1	; length of the file being read
e2FileLenLSB		DS 1	;  "

; *** Bank 9 ***
	ORG	$90

	IF	DHCP
DHCP_BANK	= $

dhcpServerId3		DS 1	; DHCP <server_identifier> = IP addr of DHCP server
dhcpServerId2		DS 1	;  "
dhcpServerId1		DS 1	;  "
dhcpServerId0		DS 1	;  "
dhcpIPLeaseTm3		DS 1	; IP lease time offered by the DHCP server
dhcpIPLeaseTm2		DS 1	;  "
dhcpIPLeaseTm1		DS 1	;  "
dhcpIPLeaseTm0		DS 1	;  "
dhcpBaseTimer1		DS 1	; DHCP base timer for renewals
dhcpBaseTimer0		DS 1	;  "
dhcpTimer3		DS 1	; DHCP timer for renewals
dhcpTimer2		DS 1	;  "
dhcpTimer1		DS 1	;  "
dhcpTimer0		DS 1	;  "
dhcpFlags		DS 1	; flags

; dhcpFlags 
DHCP_CONFIG	= 0
	ENDIF




; *** Bank A ***
	ORG	$A0

UDP_BANK	= $

udpRxSrcPortMSB		DS 1
udpRxSrcPortLSB		DS 1
udpRxDestPortMSB	DS 1	; filter value for incoming UDP packets
udpRxDestPortLSB	DS 1	;  "
udpRxDataLenMSB		DS 1	; length of <data> field of incoming UDP packet
udpRxDataLenLSB		DS 1	;  "
udpTxSrcPortMSB		DS 1
udpTxSrcPortLSB		DS 1
udpTxDestPortMSB	DS 1
udpTxDestPortLSB	DS 1
udpTxDataLenMSB		DS 1	; length of <data> field of outgoing UDP packet
udpTxDataLenLSB		DS 1	;  "

TCPTMP_BANK	= $	; stores temporary tcp info

tcpTmpAck4		DS 1	; TMP.ACK
tcpTmpAck3		DS 1	; temporary information from the received packet
tcpTmpAck2		DS 1
tcpTmpAck1		DS 1

; *** Bank B ***
	ORG	$B0

TCPSOCKET_BANK	= $	; contains the 2 TCP sockets

; socket1 - will always be bound to TCB1_BANK
sock1RemoteIP3		DS 1
sock1RemoteIP2		DS 1
sock1RemoteIP1		DS 1
sock1RemoteIP0		DS 1
sock1RemotePortMSB	DS 1
sock1RemotePortLSB	DS 1

; socket2 - will always be bound to TCB2_BANK
sock2RemoteIP3		DS 1
sock2RemoteIP2		DS 1
sock2RemoteIP1		DS 1
sock2RemoteIP0		DS 1
sock2RemotePortMSB	DS 1
sock2RemotePortLSB	DS 1

	IF	SMTP
SMTPREPLY_BANK	= $

smtpReplyCode3		DS 1	; SMTP reply code received from remote SMTP
smtpReplyCode2		DS 1	;  "
smtpReplyCode1		DS 1	;  "
	ENDIF


; *** Bank C ***
	ORG	$C0


; *** Bank D ***
	ORG	$D0


; *** Bank E ***
	ORG	$E0


; *** Bank F ***
	ORG	$F0


; ***************
; *** EQUATES ***
; ***************

INT_PERIOD	= 145	; RTCC interrupt periodicity (345kHz)
			; change this if you're not clocking the SX at 50MHz

; *** Pin Definitions ***

RA_DIR		= %10101010
RA_OUT		= %01110101
RA_LVL		= %11111111
RA_PLP		= %01111111

RB_DIR		= %10000000
RB_OUT		= %01100000
RB_LVL		= %11111111
RB_PLP		= %01111111

RC_DIR		= %11111111
RC_OUT		= %00000000
RC_LVL		= %11111111
RC_PLP		= %11111111

RD_DIR		= %11111111
RD_OUT		= %00000000
RD_LVL		= %11111111
RD_PLP		= %00000000

RE_DIR		= %01111111
RE_OUT		= %00000000
RE_LVL		= %00111111
RE_PLP		= %11000000

NIC_DATA_PORT	= rc
NIC_CTRL_PORT	= rb

IOWB_PIN	= NIC_CTRL_PORT.5
IORB_PIN	= NIC_CTRL_PORT.6
IOCH_PIN	= NIC_CTRL_PORT.7

E2_PORT		= ra

E2SCL		= 4
E2SDA		= 5
E2SCL_PIN	= E2_PORT.E2SCL
E2SDA_PIN	= E2_PORT.E2SDA

LED_PORT	= ra
LED		= 6
LED_PIN		= LED_PORT.LED

SW_PORT		= ra
SW		= 7
SW_PIN		= SW_PORT.SW

ADC_PORT	= re

ADC_OUT		= 7
ADC_IN		= 6

ADC_OUT_PIN	= ADC_PORT.ADC_OUT
ADC_IN_PIN	= ADC_PORT.ADC_IN


; *** flags ***

RX_IS_ARP	= 0	; incoming packet is an ARP packet
RX_IS_ICMP	= 1	; incoming packet is an ICMP packet
RX_IS_UDP	= 2	; incoming packet is a UDP packet
RX_IS_TCP	= 3	; incoming packet is a TCP packet
RX_IS_IP_BCST	= 4	; incoming packet is an IP Broadcast packet
GOT_DHCP_OFFER	= 5	; received DHCP IP address offer
GOT_IP_ADDR	= 6	; received an IP address assignment (recv'ed DHCP ACK)
IP_CHKSUM_LSB	= 7	; next byte to accumulate IP checksum is LSB
TCP_CHKSUM_LSB	= 5	; next byte to accumulate TCP checksum is LSB

; *** flags2 ***

GOT_RX_FRAME	= 0	; used by NICWaitRxFrame to indicate we got one. 
GOT_IP_LEASE	= 1	; indicates that an IP lease was granted by a DHCP server
RENEW_IP_LEASE	= 2	; indicates an IP lease renewal is in progress
TCP_SOCK	= 3	; tcp connection/socket indicator. 0=tcp1, 1=tcp2
TCP_TXSEMA	= 4	; tcp transmit semaphore.
SMTP_TXEN	= 5	; SMTP transmit enable
SW_PRESSED	= 6	; indicates the switch SW2 is held in (pressed) when 1
BROWSER_TYPE	= 7	; 1=netscape type, 0=IE type

; *** flags3 ***

ARP_REQ_SENT	= 0	; indicates that an ARP request has been sent
ARP_RSP_RCVD	= 1	; indicates that an ARP response has been received
ARP_STL_TX	= 2	; indicates that the stalled packet is to be transmitted
SMTP_OK		= 3	; indicates that SMTP Delivered mail successfully
HTTP_METHOD	= 4	; 0=get, 1=post/put (requested method by remote browser)
GOT_HTTP_METHOD	= 5	; indicates the HTTP_METHOD bit is valid
GOT_URI		= 6	; indicates the file URI is complete
LED_LOCK	= 7	; locks access to the LED when set to 1.

; *** NIC Constants ***

RXBUF_START	= $40	; 3328 byte receive buffer (2 max-size packets)
RXBUF_END	= $4D	;  "

TXBUF1_START	= $4D	; 1536 byte transmit buffer for ICMP/UDP
TXBUF2_START	= $53	; 1536 byte transmit buffer for tcp connection1
TXBUF3_START	= $59	; 1536 byte transmit buffer for tcp connection2
TXBUF4_START	= $5F	; 256 byte transmit buffer for ARP

; *** Ethernet Constants ***

; also see in "DEMO DEFINES" section

; *** ARP Constants ***

ARP_TIMEOUT	= 5	; waiting for an ARP response timeout period

; *** IP Constants ***

; also see in "DEMO DEFINES" section

IP_TTL = 32

; *** DHCP Constants ***
	IF	DHCP
DHCP_DISC_EXP	= 3	; timeout in seconds waiting for an OFFER
DHCP_REQ_EXP	= 3	; timeout in seconds waiting for an ACK after REQUEST
DHCP_REQ_TRIES	= 3	; no of retries in txing a DHCPREQUEST
	ENDIF

; *** UDP Constants ***

UDP_RX_DEST_MSB	= $04	; user UDP RX Port: 1025
UDP_RX_DEST_LSB	= $01	;  "

; *** TCP Constants ***

; TCP state-machine states (numbering order is signifcant)
TCP_ST_CLOSED	= 0
TCP_ST_LISTEN	= 1
TCP_ST_SYNSENT	= 2
TCP_ST_SYNRCVED	= 3
TCP_ST_ESTABED	= 4
TCP_ST_FINWAIT1	= 5
TCP_ST_FINWAIT2	= 6
TCP_ST_CLOSEWAIT= 7
TCP_ST_CLOSING	= 8
TCP_ST_LASTACK	= 9
TCP_ST_TIMEWAIT	= 10

; Bit positions in the TCP <flag> byte.
TCP_FLAG_ACK	= 4
TCP_FLAG_PSH	= 3
TCP_FLAG_RST	= 2
TCP_FLAG_SYN	= 1
TCP_FLAG_FIN	= 0

; TCP Options
TCP_OPTION_END	= 0
TCP_OPTION_NOP	= 1
TCP_OPTION_MSS	= 2	; max segment size

TCP_HDR_LENGTH	= 5	; normal TCP header length.
TCP_OFFSET_MASK	= $F0

TCP_WINDOW_SIZE	= 1400	; max # of Data bytes we will accept
TCP_SEG_SIZE	= 1400	; max # of Data bytes TCP will transmit per segment

TCP_RESTART_EXP	= 8	; TCP re-transmission timeout period (0.19s per tick)
TCP_CONN_EXP	= 160	; TCP connection timeout period (0.19s per tick)

; *** HTTP Constants ***

; States for parsing HTTP headers

HTTP_PORT_MSB	= 0	; local tcp port number for HTTP server.
HTTP_PORT_LSB	= 80	;  "

HTTP_SEG_SIZE	= 1400

URI1		= $81	; hash of "resource.htm"
URI2		= $54	; hash of "temperature.htm"
URI3		= $21	; hash of "postctrl.htm"

; *** EEPROM Constants ***

E2_CMD_RD	= $A1	; most-significant 7-bits is the I2C slave addr
E2_CMD_WR	= $A0	; most-significant 7-bits is the I2C slave addr

E2_SDA_MASK	= (1 << E2SDA)	; a '1' in the SDA bit, '0' everywhere else

E2_DDR_SDA_IN	= (RA_DIR | E2_SDA_MASK)	; direction of SDA port when SDA is input

E2_DDR_SDA_OUT	= (RA_DIR & (~E2_SDA_MASK))	; direction of SDA port when SDA is output

; *** SMTP Constants ***

SMTP_PORT_MSB	= 0	; remote tcp port number for SMTP server.
SMTP_PORT_LSB	= 25	;  "

; SMTP sendmail state-machine states

SMTP_CLOSE	= 0	; smtp closing or closed
SMTP_CONNECT	= 1	; state machine in connection establishment
SMTP_HELO	= 2	; HELO command sent, waiting for 250 reply
SMTP_MAIL	= 3	; MAIL FROM command sent, waiting for 250 reply
SMTP_RCPT	= 4	; RECIPIENT TO command sent, waiting for 250 reply
SMTP_DATA	= 5	; DATA command sent, waiting for 354 reply
SMTP_TEXT	= 6	; sent email text message, waiting for ack
SMTP_QUIT	= 7	; sent QUIT command, waiting for 221 reply

; email constants
CR		= $0D	; Control
LF		= $0A	; Line Feed


; **************
; *** MACROS ***
; **************

_bank	MACRO	1		; sets FSR[7:4]
		bank	\1
	IF \1 &	%10000000	; SX48BD and SX52BD (production release)
				; BANK instruction
		setb	fsr.7	; modifies FSR bits 4,5 and 6. FSR.7 needs
				; to be set by sw.
	ELSE
		clrb	fsr.7
	ENDIF
	ENDM

_banky	MACRO	1		; set FSR[7] ~only~
	IF \1 &	%10000000	; SX48BD and SX52BD (production release)
				; bank instruction
		setb	fsr.7	; modifies FSR bits 4,5 and 6. FSR.7 needs to
				; be set by sw.
	ELSE
		clrb	fsr.7
	ENDIF
	ENDM

_mode	MACRO	1
		mov	w, #\1	; loads the M register correctly for the SX48BD
				; and SX52BD
		mov	m, w
	ENDM

_pc_check	MACRO	0
	IF ($ &	$100)
		ERROR 'ERROR!! ADD PC,W	instruction at invalid addr'
	ENDIF
	ENDM


; ***********
; *** ISR ***
; ***********
	ORG	0	; Page0

ISR

Timer		; implement various SW timers

		; lowest-common-denominator timer
		_bank	TIMER_BANK
		incsz	baseTimer	; inc at 2.9us per tick with 50MHz clk
		jmp	:timerEnd

:tcp1Timer	; TCP1-timer (used for TCP1 re-transmission timeouts)
		incsz	tcp1TimerLSB	; inc at 742.4us per tick with 50MHz clk
		jmp	:tcp2Timer
		inc	tcp1TimerMSB	; inc at 190.0544ms per tick with 50MHz clk

:tcp2Timer	; TCP2-timer (used for TCP2 re-transmission timeouts)
		incsz	tcp2TimerLSB	; inc at 742.4us per tick with 50MHz clk
		jmp	:conn1Timer
		inc	tcp2TimerMSB	; inc at 190.0544ms per tick with 50MHz clk

:conn1Timer	; Connection-timer (used for TCP1 connection timeouts)
		incsz	conn1TimerLSB
		jmp	:conn2Timer
		inc	conn1TimerMSB

:conn2Timer	; Connection-timer (used for TCP2 connection timeouts)
		incsz	conn2TimerLSB
		jmp	:arpTimer
		inc	conn2TimerMSB

:arpTimer	; ARP-timer (used for ARP response timeouts)
		incsz	arpTimerLSB
		jmp	:dhcpTimer
		inc	arpTimerMSB

:dhcpTimer      
	IF	DHCP    
		_bank   DHCP_BANK
		; DHCP-timer (used for IP lease renewals)
		inc     dhcpBaseTimer0      ; inc at 742.4us per tick 
                                    ; w 50MHz clk
		snz
		inc     dhcpBaseTimer1      ; inc at 190.0544ms per tick 
                                    ; w 50MHz clk
		mov     w, dhcpBaseTimer1   ; check if 1s has passed
		xor     w, #5               ; 5 ticks = 0.950272s
		jnz     :timerEnd
		mov     w, dhcpBaseTimer0
		xor     w, #63              ; 63 ticks = 0.0467712s
		jnz     :timerEnd
		clr     dhcpBaseTimer0      ; reset 1s counters
		clr     dhcpBaseTimer1         
		incsz   dhcpTimer0      ; inc at 1s per tick
		jmp     :timerEnd
		incsz   dhcpTimer1      ; inc at 256s per tick
		jmp     :timerEnd
		incsz   dhcpTimer2      ; inc at 65536s per tick
		jmp     :timerEnd
		inc     dhcpTimer3      ; inc at 16777216s per tick                
	ENDIF

:timerEnd

ADCTempSensor	; SW A-to-D for measuring current board temperature to be then
		; displayed on a Dynamic web page

		; ADC(out) = !ADC(in) (balancing the yin and the yang)
		mov	w, ADC_PORT
		sb	wreg.ADC_IN
		setb	ADC_OUT_PIN
		snb	wreg.ADC_IN
		clrb	ADC_OUT_PIN

		; decision time
		_bank	ADC_BANK
		sb	wreg.ADC_OUT
		incsz	adcAcc
		inc	adcAcc
		dec	adcAcc

		inc	adcCount
		jnz	:adcTempSensorEnd

		; accumulate for averaging (256 samples)
		mov	w, adcAcc
		clr	adcAcc
		add	adcLSB, w
		snc
		inc	adcMSB

		; check if averaging is done
		incsz	adcSampleCount
		jmp	:adcTempSensorEnd

		; averaging Done -- save results
		mov	adc, adcMSB	; divide by 256 (clever huh?)
		clr	adcMSB
		clr	adcLSB
:adcTempSensorEnd

LedBlinker	; blinks auxilliary LED, but Don't interfere if E2 access is
		; in progress because LED and E2 may share same port

		; do not blink if LED locked
		jb	flags3.LED_LOCK, :ledBlinkerEnd

	IF	HTTP
		_bank	HTTP_BANK
		test	httpParseState	; do not blink led if E2 access going
		jnz	:ledBlinkerEnd
	ENDIF
		_bank	TIMER_BANK
		mov	w, tcp2TimerMSB	; use the tcp2 timer
					; for led flash rate
		and	w, #%00001111
		jnz	:ledHere
		_bank	MISC_BANK
		clrb	LED_PORT.LED	; switch led on
		jmp	:ledBlinkerEnd

:ledHere	mov	w, tcp2TimerMSB
		and	w, #%00001111
		xor	w, #1
		jnz	:ledBlinkerEnd
		_bank	MISC_BANK
		setb	LED_PORT.LED	; switch led off
:ledBlinkerEnd

ISRExit		mov	w, #-INT_PERIOD&255
		retiw


; ********************
; *** MAIN PROGRAM ***
; ********************

Init			jmp	_Init
ARPInit			jmp	_ARPInit
TCPIPInit		jmp	_TCPIPInit
StartupDelay		jmp	_StartupDelay
CopyRemoteIPSocket1	jmp	_CopyRemoteIPSocket1
CopyRemoteIPSocket2	jmp	_CopyRemoteIPSocket2
CheckRemotePortTCB1	jmp	_CheckRemotePortTCB1
CheckRemotePortTCB2	jmp	_CheckRemotePortTCB2
	IF	HTTP
E2Init			jmp	_E2Init
	ENDIF


Main
		call	@Init
	IF	DHCP
		call	@DHCPConfig		; configure iSX via DHCP server
	ENDIF

		call	@UDPAppInit		; initialise UDP application

		_bank	MISC_BANK
		clr	pageCount		; clear page counter (dynamic Data)

; main program loop
:mainLoop
	IF	DHCP
		call	@CheckIPLeaseExpire	;	check validity of IP lease
	ENDIF
		call	@NICCheckRxFrame	; check if we received a frame
		jz	:noRxFrame		; no

		call	@NICWaitRxFrame		; no waiting cus we've already
						; checked

		call	@ARPCheckIfIs		; check and process if ARP
		jb	flags.RX_IS_ARP, :mainLoop

		call	@CheckIPDatagram	; not ARP, check if IP

		jb	flags.RX_IS_ICMP, :icmp
		jb	flags.RX_IS_UDP, :udp
		jb	flags.RX_IS_TCP, :tcp

		jmp	:mainLoop

:icmp		call	@ICMPProcPktIn		; process incoming ICMP packet
		jmp	:mainLoop

:udp		call	@UDPProcPktIn		; process incoming UDP packet
		jmp	:mainLoop

:tcp		call	@TCPProcPktIn		; process incoming TCP packet
		jmp	:mainLoop

:noRxFrame	call	@ARPSendStPacket	; send ARP stalled packets if any

		bank	TCP_BANK
		cje	tcp2State, #TCP_ST_CLOSED, :tcp2Closed	; check if tcp2
								; in listen
		cje	tcp2State, #TCP_ST_LISTEN, :tcp2Listen	; check if tcp2
								; closed

		; tcp2 is in another state, check if hanging connection
		bank	TIMER_BANK
		cjae	conn2TimerMSB, #TCP_CONN_EXP, :resetTCP2
		jmp	:tcpTx			; connection timer not timed out

:tcp2Closed	call	@TCPApp2Init		; initialise tcp2 application
		_bank	TIMER_BANK
		clr	conn2TimerMSB		; clear the connection timer
		jmp	:tcp1Check

:tcp2Listen	bank	TIMER_BANK
		clr	conn2TimerMSB		; tcp2 in listen, clr conn timer
		jmp	:tcp1Check

:tcp1Check	_bank	TCP_BANK
		cje	tcp1State, #TCP_ST_CLOSED, :tcp1Closed	; check if tcp1
								; in listen
		cje	tcp1State, #TCP_ST_LISTEN, :tcp1Listen	; check if tcp1
								; closed

		; tcp1 is in another state, check if hanging connection
		bank	TIMER_BANK
		cjae	conn1TimerMSB, #TCP_CONN_EXP, :resetTCP1
		jmp	:tcpTx			; connection timer not timed out

:tcp1Closed	call	@TCPApp1Init		; initialise tcp1 application
		_bank	TIMER_BANK
		clr	conn1TimerMSB		; clear the connection timer
		jmp	:mainLoop

:tcp1Listen	bank	TIMER_BANK
		clr	conn1TimerMSB		; tcp2 in listen, clr conn timer
		jmp	:mainLoop

:resetTCP2	; reset hung TCP2 connection
		bank	TCP_BANK
		clr	tcp2UnAckMSB		; clear bytes to still send on tcp2
		clr	tcp2UnAckLSB
		mov	tcp2State, #TCP_ST_CLOSED
	IF	HTTP
		bank	HTTP_BANK		; reset HTTP webserver state
		clr	httpParseState
	ENDIF
		jmp	:mainLoop

:resetTCP1	; reset hung TCP1 connection
		bank	TCP_BANK
		clr	tcp1UnAckMSB		; clear bytes to still send on tcp1
		clr	tcp1UnAckLSB
		mov	tcp1State, #TCP_ST_CLOSED
	IF	SMTP
		bank	SMTP_BANK		; reset SMTP client state
		clr	smtpState
	ENDIF
		jmp	:mainLoop		; repeat cycle by returning to entry

		; do not allow new tx if waiting for ARP response
:tcpTx		jb	flags3.ARP_REQ_SENT, :mainLoop
		mov	w, #(1<<TCP_TXSEMA)
		xor	flags2,	w		; toggle TCP_TXSEMA to give other tcp
						; connection a time-slice to transmit
		call	@TCPTransmit		; check if app has anything to transmit
		jmp	:mainLoop		; repeat cycle by returning to entry


; *******************
; *** SUBROUTINES ***
; *******************

	ORG	$D0	; Page0

; ******************************************************************************
_Init
; Main program initialization code
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		_mode	LVL_W
		mov	!ra, #RA_LVL
		mov	!rb, #RB_LVL
		mov	!rc, #RC_LVL
		mov	!rd, #RD_LVL
		mov	!re, #RE_LVL

		_mode	PLP_W
		mov	!ra, #RA_PLP
		mov	!rb, #RB_PLP
		mov	!rc, #RC_PLP
		mov	!rd, #RD_PLP
		mov	!re, #RE_PLP

		_mode	DIR_W
		mov	!ra, #RA_DIR
		mov	!rb, #RB_DIR
		mov	!rc, #RC_DIR
		mov	!rd, #RD_DIR
		mov	!re, #RE_DIR

		mov	ra, #RA_OUT
		mov	rb, #RB_OUT
		mov	rc, #RC_OUT
		mov	rd, #RD_OUT
		mov	re, #RE_OUT

		clr	flags
		clr	flags2
		clr	flags3

		call	@NICInit
		call	@ARPInit
		call	@TCPIPInit
	IF	HTTP
		call	@E2Init
	ENDIF
		mov	w, #(RTCC_PS_OFF)	; setup option register
		mov	!option, w

		retp

; ******************************************************************************
_ARPInit
; ARP initialization code
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		_bank	ARP_BANK
		clr	host1IP0	; clear the cache
		clr	host1IP1	;  "
		clr	host1IP2	;  "
		clr	host1IP3	;  "
		retp

; ******************************************************************************
_TCPIPInit
; TCP/IP stack initialization code
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		_bank	IP_BANK

	IF	DHCP
		clr	myIP3
		clr	myIP2
		clr	myIP1
		clr	myIP0
	ELSE
		; initialize SX's IP addr
		setb	flags.GOT_IP_ADDR
		mov	myIP3, #SX_IP_ADDR3
		mov	myIP2, #SX_IP_ADDR2
		mov	myIP1, #SX_IP_ADDR1
		mov	myIP0, #SX_IP_ADDR0
	ENDIF
		; initialize IP Identifier sequence number
		clr	ipIdentMSB
		clr	ipIdentLSB

		; initialise the TCP variables
		bank	TCP_BANK
		clr	tcp1UnAckMSB
		clr	tcp1UnAckLSB
		clr	tcp2UnAckMSB
		clr	tcp2UnAckLSB
		mov	tcp1State, #TCP_ST_CLOSED
		mov	tcp2State, #TCP_ST_CLOSED

		; clear the tcp socket local ports (used for listening on).
		bank	TCB1_BANK
		clr	tcb1LocalPortLSB
		clr	tcb1LocalPortMSB

		bank	TCB2_BANK
		clr	tcb2LocalPortLSB
		clr	tcb2LocalPortMSB

		; clear the tcp sockets remote IP addresses
		_bank	TCPSOCKET_BANK
		clr	sock1RemoteIP3
		clr	sock2RemoteIP3

		retp

	IF	HTTP
; ******************************************************************************
_E2Init
; EEPROM initialization code
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		; get the I2C Device into a known state
		call	@E2SDAInput
:e2InitLoop	clrb	E2SCL_PIN
		call	@E2Delay1300ns
		setb	E2SCL_PIN
		call	@E2Delay900ns
		jnb	E2SDA_PIN, :e2InitLoop
		retp
	ENDIF

; ******************************************************************************
_StartupDelay
; Delay for ?ms @ 50MHz
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		mov	globTemp2, #10
:loop3		clr	globTemp1
:loop2		clr	w
:loop1		decsz	wreg
		jmp	:loop1
		decsz	globTemp1
		jmp	:loop2
		decsz	globTemp2
		jmp	:loop3
		retp

; ******************************************************************************
_CopyRemoteIPSocket1
; Copies remoteIP3-0 into sock1RemoteIP3
; INPUT: remoteIP3-0
; OUTPUT: sock1RemoteIP3
; ******************************************************************************
		mov	globTemp1, #sock1RemoteIP3
		mov	globTemp3, #remoteIP3
		call	@Copy4Inc
		retp

; ******************************************************************************
_CopyRemoteIPSocket2
; Copies remoteIP3-0 into sock2RemoteIP3
; INPUT: remoteIP3-0
; OUTPUT: sock2RemoteIP3
; ******************************************************************************
		mov	globTemp1, #sock2RemoteIP3
		mov	globTemp3, #remoteIP3
		call	@Copy4Inc
		retp

; ******************************************************************************
_CheckRemotePortTCB1
; Checks if the remote port in the TCP packet is in TCB1_BANK
; INPUT: tcpRemotePortLSB, tcpRemotePortMSB, tcb1RemotePortLSB,
;	 tcb1RemotePortMSB
; OUTPUT: Z set if in there
; ******************************************************************************
		_bank	TCPPORT_BANK
		mov	w, tcpRemotePortMSB
		bank	TCB1_BANK
		xor	w, tcb1RemotePortMSB
		sz
		retp
		mov	w, tcb1RemotePortLSB
		bank	TCPPORT_BANK
		xor	w, tcpRemotePortLSB
		retp

; ******************************************************************************
_CheckRemotePortTCB2
; Checks if the remote port in the TCP packet is in TCB2_BANK
; INPUT: tcpRemotePortLSB, tcpRemotePortMSB, tcb2RemotePortLSB,
; tcb2RemotePortMSB
; OUTPUT: Z set if in there
; ******************************************************************************
		_bank	TCPPORT_BANK
		mov	w, tcpRemotePortMSB
		bank	TCB2_BANK
		xor	w, tcb2RemotePortMSB
		sz
		retp
		mov	w, tcb2RemotePortLSB
		bank	TCPPORT_BANK
		xor	w, tcpRemotePortLSB
		retp

	IF	SMTP
; ******************************************************************************
_senderDomainName
; Contains the sender's domain name.
; [SMTP API function]
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
SMTPTEXT_HELO	=	$
		dw	'HELO '
		; insert the Domain name here
		dw	'sx'
		dw	CR,LF
SMTPTEXT_HELO_END	=	$


; ******************************************************************************
_mailFrom
; Contains the sender's address.
; [SMTP API function]
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
SMTPTEXT_MAIL	=	$
		dw	'MAIL FROM: '
		; insert the sender's address here
		dw	'<sx>'
		dw	CR,LF
SMTPTEXT_MAIL_END	=	$


; ******************************************************************************
_mailTo
; Contains the recipient's address.
; [SMTP API function]
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
SMTPTEXT_RCPT	=	$
		dw	'RCPT TO: '
		; insert the recipient's address here
		dw	'<joe@demo.sx>'
		dw	CR,LF
SMTPTEXT_RCPT_END	=	$

	ENDIF

	IF	SMTP
; ******************************************************************************
_mailData
; Contains the mail message Data.
; [SMTP API function]
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
SMTPTEXT_TEXT	=	$
		dw	'From: SX'
		dw	CR,LF
		; insert To: field here
		dw	'To: Joe'
		dw	CR,LF
		; insert Subject: field here
		dw	'Subject: Button Pressed!'
		dw	CR,LF,CR,LF
		; insert body of email message here
		dw	'Button SW2 pressed'
		dw	CR,LF,'.',CR,LF
SMTPTEXT_TEXT_END	=	$
	ENDIF

	IF	SMTP
SMTPTEXT_QUIT	=	$
_SMTPTEXT_QUIT	dw	'QUIT',$0D,$0A
	ENDIF


	ORG	$200	; Page1

NICBufCopy	jmp	_NICBufCopy
NICBufWrite	jmp	_NICBufWrite
NICBufRead	jmp	_NICBufRead
NICBufIPAddrWr	jmp	_NICBufIPAddrWr
NICWriteSrcIP	jmp	_NICWriteSrcIP
NICWriteDestIP	jmp	_NICWriteDestIP
NICWriteSrcEth	jmp	_NICWriteSrcEth
NICWriteDestEth	jmp	_NICWriteDestEth
NICDMAInit	jmp	_NICDMAInit

; ******************************************************************************
NICInit
; Initializes and configures Realtek RTL8019AS NIC
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		_bank	NIC_BANK

		mov	nicIOAddr, #$1F		; write to reset port
		mov	w, #0
		call	NICWrite
		call	@StartupDelay		; give it a little time to reset

		; --- Page3 Registers ---

		clr	nicIOAddr		; CR
		mov	w, #%11000001		; Page3, Stop
		call	NICWrite

		mov	nicIOAddr, #$01		; 9346CR
		mov	w, #%11000000		; config register write enable
		call	NICWrite

		mov	nicIOAddr, #$05		; CONFIG2
		mov	w, #%00000000		; link test enable
		call	NICWrite

		; --- Page1 Registers ---

		clr	nicIOAddr		; CR
		mov	w, #%01000001		; Page1, Stop
		call	NICWrite

		inc	nicIOAddr		; ($01) PAR0
		mov	w, #SX_ETH_ADDR0
		call	NICWrite
		inc	nicIOAddr		; ($02) PAR1
		mov	w, #SX_ETH_ADDR1
		call	NICWrite
		inc	nicIOAddr		; ($03) PAR2
		mov	w, #SX_ETH_ADDR2
		call	NICWrite
		inc	nicIOAddr		; ($04) PAR3
		mov	w, #SX_ETH_ADDR3
		call	NICWrite
		inc	nicIOAddr		; ($05) PAR4
		mov	w, #SX_ETH_ADDR4
		call	NICWrite
		inc	nicIOAddr		; ($06) PAR5
		mov	w, #SX_ETH_ADDR5
		call	NICWrite

		inc	nicIOAddr		; ($07) CURR
		mov	w, #RXBUF_START
		call	NICWrite

		; --- Page0 Registers ---

		clr	nicIOAddr		; CR
		mov	w, #%00000001		; Page0, Stop
		call	NICWrite

		inc	nicIOAddr		; ($01) PSTART
		mov	w, #RXBUF_START
		call	NICWrite

		inc	nicIOAddr		; ($02) PSTOP
		mov	w, #RXBUF_END
		call	NICWrite

		inc	nicIOAddr		; ($03) BNRY
		mov	w, #RXBUF_START
		call	NICWrite

		mov	nicIOAddr, #$07		; ISR
		mov	w, #$FF
		call	NICWrite

		mov	nicIOAddr, #$0C		; RCR
		mov	w, #%11000110
		call	NICWrite

		inc	nicIOAddr		; ($0D) TCR
		mov	w, #%11100000
		call	NICWrite

		inc	nicIOAddr		; ($0E) DCR
		mov	w, #%10111000
		call	NICWrite

		clr	nicIOAddr		; CR
		mov	w, #%00000010		; Page0, Start
		call	NICWrite

		retp

; ******************************************************************************
NICWrite
; Does an I/O Write of a byte on the ISA host bus to the NIC
; INPUT:  w = byte to be written
;	  nicIOAddr = I/O address (most-significant 3 bits must be zero)
; OUTPUT: none
; ******************************************************************************
		bank	NIC_BANK

		; put Data out on Data bus
		mov	NIC_DATA_PORT, w
		_mode	DIR_W
		mov	w, #0			; output
		mov	!NIC_DATA_PORT,	w

		; put addr out on addr bus
		mov	w, NIC_CTRL_PORT
		and	w, #%11100000
		or	w, nicIOAddr
		mov	NIC_CTRL_PORT, w

		; strobe IOWB pin
		jmp	$+1
		clrb	IOWB_PIN
		jmp	$+1
		jnb	IOCH_PIN, $
		setb	IOWB_PIN

		retp

; ******************************************************************************
NICWriteAgain
; Write to the same nicIOAddr as the previous call to NICWrite()
; INPUT:  w = byte to be written
; OUTPUT: none
; ******************************************************************************
		; put Data out on Data bus
		mov	NIC_DATA_PORT, w
		; strobe IOWB pin
		jmp	$+1
		clrb	IOWB_PIN
		jmp	$+1
		jnb	IOCH_PIN, $
		setb	IOWB_PIN
		retp

; ******************************************************************************
NICRead
; Does an I/O Read of a byte on the ISA host bus from the NIC
; INPUT:  nicIOAddr = I/O address (most-significant 3 bits must be zero)
; OUTPUT: w = byte read
; ******************************************************************************
		bank	NIC_BANK

		; configure Data bus for input
		_mode	DIR_W
		mov	w, #$FF			; input
		mov	!NIC_DATA_PORT,	w

		; put addr out on addr bus
		mov	w, NIC_CTRL_PORT
		and	w, #%11100000
		or	w, nicIOAddr
		mov	NIC_CTRL_PORT, w

		; strobe IORB pin and latch Data
		jmp	$+1
		clrb	IORB_PIN
		jmp	$+1
		jnb	IOCH_PIN, $
		mov	w, NIC_DATA_PORT
		setb	IORB_PIN

		retp

; ******************************************************************************
NICReadAgain
; Read the NIC using the same nicIOAddr as the previous call to NICRead()
; INPUT:  none
; OUTPUT: w = byte read
; ******************************************************************************
		; strobe IORB pin and latch Data
		jmp	$+1
		clrb	IORB_PIN
		jmp	$+1
		jnb	IOCH_PIN, $
		mov	w, NIC_DATA_PORT
		setb	IORB_PIN
		retp

; ******************************************************************************
NICPseudoRead
; 'Read' the NIC, but ignore Data. Must have called NICRead() or NICReadAgain()
; priorly
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		; strobe IORB pin
		jmp	$+1
		clrb	IORB_PIN
		jmp	$+1
		jnb	IOCH_PIN, $
		setb	IORB_PIN
		retp

; ******************************************************************************
NICPseudoRead6
; 'Read' the NIC (6) times, but ignore Data. Must have called NICRead() or
; NICReadAgain() priorly
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		mov	w, #6
:loop		call	NICPseudoRead
		decsz	wreg
		jmp	:loop
		retp

; ******************************************************************************
NICCheckRxFrame
; Checks to see if an ethernet frame has been received
; INPUT:  none
; OUTPUT: z is cleared if there's a frame waiting, otherwise z is set
; ******************************************************************************
		_bank	NIC_BANK

		clr	nicIOAddr		; CR
		mov	w, #%01100010		; Page1, abort DMA
		call	NICWrite

		mov	nicIOAddr, #$07		; CURR
		call	NICRead
		mov	globTemp1, w

		clr	nicIOAddr		; CR
		mov	w, #%00000010		; Page0
		call	NICWrite

		mov	nicIOAddr, #$03		; BNRY
		call	NICRead
		xor	w, globTemp1		; CURR = BNRY => no packets
		retp

; ******************************************************************************
NICWaitRxFrame
; Wait for an ethernet frame to be received.
; INPUT:  none
; OUTPUT: nicCurrPktPtr = points to beginning of packet just received
;	  nicNextPktPtr = points to beginnig of next packet
;	  nicRemoteEth0-5 = source (remote) ethernet address
; ******************************************************************************
		clrb	flags2.GOT_RX_FRAME	; clear indication of prev.
						; rcvd frame

		_bank	NIC_BANK

:loop		clr	nicIOAddr		; CR
		mov	w, #%01100010		; Page1, abort DMA
		call	NICWrite

		mov	nicIOAddr, #$07		; CURR
		call	NICRead
		mov	globTemp1, w

		clr	nicIOAddr		; CR
		mov	w, #%00000010		; Page0
		call	NICWrite

		mov	nicIOAddr, #$03		; BNRY
		call	NICRead
		xor	w, globTemp1

		snz				; CURR = BNRY => no packets
		retp				; Return immediately

		setb	flags2.GOT_RX_FRAME	; indicate we got something

		clr	nicIOAddr		; CR
		mov	w, #%00011010		; Page0, packet send
		call	NICWrite

		; store current-packet pointer
		mov	nicIOAddr, #$03		; BNRY
		call	NICRead
		mov	nicCurrPktPtr, w

		mov	nicIOAddr, #$10		; RDMA

		; ignore receive status
		call	NICRead

		; store next-packet pointer
		call	NICReadAgain
		mov	nicNextPktPtr, w

		; ignore ethernet frame size
		call	NICPseudoRead
		call	NICPseudoRead

		; ignore the <destination> ethernet addr
		call	NICPseudoRead6

		; record the sender's <source> ethernet addr
		call	NICReadAgain
		mov	nicRemoteEth0, w
		call	NICReadAgain
		mov	nicRemoteEth1, w
		call	NICReadAgain
		mov	nicRemoteEth2, w
		call	NICReadAgain
		mov	nicRemoteEth3, w
		call	NICReadAgain
		mov	nicRemoteEth4, w
		call	NICReadAgain
		mov	nicRemoteEth5, w

		clr	nicIOAddr		; CR
		mov	w, #%00100010		; Page0, abort DMA
		call	NICWrite

		retp

; ******************************************************************************
NICDumpRxFrame
; Discard the current received frame by advancing the receive circular buffer
; tail pointer
; INPUT:  nicNextPktPtr = next packet page
; OUTPUT: none
; ******************************************************************************
		bank	NIC_BANK

		clr	nicIOAddr		; CR
		mov	w, #%00100010		; Page0, abort DMA
		call	NICWrite

		mov	nicIOAddr, #$03		; BNRY
		mov	w, nicNextPktPtr	; advance tail pointer
		call	NICWrite

		retp

; ******************************************************************************
NICInitTxFrame
; i. initialize NIC for remote-DMA writes
; ii. fills in ethernet <destination> and <source>
; INPUT:  w = starting page number of tx buffer
;	  nicRemoteEth0-5 = Destination ethernet address
; OUTPUT: none
; ******************************************************************************
		mov	globTemp1, w

		bank	NIC_BANK

		; wait for prior transmission to complete
		clr	nicIOAddr		; CR
:wait		call	NICRead
		jb	wreg.2,	:wait

		mov	w, #%00100010		; Page0, abort DMA
		call	NICWrite

		mov	nicIOAddr, #$04		; TPSR
		mov	w, globTemp1
		call	NICWrite

		mov	nicIOAddr, #$08		; RSAR0
		mov	w, #$00
		call	NICWrite
		inc	nicIOAddr		; ($09) RSAR1
		mov	w, globTemp1
		call	NICWrite

		mov	nicIOAddr, #$0A		; RBCR0
		mov	w, #$EA			; max ethernet packet size
		call	NICWrite
		inc	nicIOAddr		; ($0B) RBCR1
		mov	w, #$05
		call	NICWrite

		clr	nicIOAddr		; CR
		mov	w, #%00010010		; Page0, remote write
		call	NICWrite

		mov	nicIOAddr, #$10		; RDMA

		; <destination>
		call	NICWriteDestEth

		; <source>
		call	NICWriteSrcEth

		retp

; ******************************************************************************
NICSendTxFrame
; Once the transmit buffer on the NIC is filled, call this to tell the NIC to
; start sending
; INPUT:  {ipLengthMSB,ipLengthLSB} = length of IP frame to be transmitted
; OUTPUT: none
; ******************************************************************************
		call	@ARPCheckCache		; start ARP routine
		snb	flags3.ARP_REQ_SENT	; Continue if an ARP request
						; was not sent

		retp				; exit, ARP request was sent
						; or we are still waiting
						; for a response

; an entry point into the function to skip the ARP routines
; no code space left in this bank to elegantly check a bit
NICSendTxFrameNow
		bank	NIC_BANK

		clr	nicIOAddr		; CR
		mov	w, #%00100010		; Page0, abort DMA
		call	NICWrite

		bank	IP_BANK
		mov	w, #(64-6-6-2)
		mov	w, ipLengthLSB-w
		jc	:notRunt
		mov	w, #1
		mov	w, ipLengthMSB-w
		jc	:notRunt

		bank	NIC_BANK
		mov	nicIOAddr, #$05		; TBCR0
		mov	w, #64			; min ethernet frame size
		call	NICWrite
		inc	nicIOAddr		; ($06) TBCR1
		mov	w, #0
		call	NICWrite
		jmp	:transmit

:notRunt	bank	NIC_BANK
		mov	nicIOAddr, #$05		; TBCR0
		bank	IP_BANK
		mov	w, #(6+6+2)
		add	w, ipLengthLSB
		call	NICWrite		; should not affect carry flag

		inc	nicIOAddr		; ($06) TBCR1
		bank	IP_BANK
		mov	w, ipLengthMSB
		snc
		inc	wreg
		call	NICWrite

:transmit	clr	nicIOAddr		; CR
		mov	w, #%00000110		; Page0, transmit
		call	NICWrite

		retp

; ******************************************************************************
_NICBufCopy
; Copy one part of the NIC's SRAM buffer to another part
; INPUT:  {nicCopySrcMSB,nicCopySrcLSB} = source address in NIC's SRAM
;	  {nicCopyDestMSB,nicCopyDestLSB} = Destination address in NIC's SRAM
;	  {nicCopyLenMSB,nicCopyLenLSB} = length of buffer to copy
; OUTPUT: none
; ******************************************************************************
		bank	NIC_BANK

		clr	nicIOAddr		; CR
		mov	w, #%00100010		; Page0, abort DMA
		call	NICWrite

		mov	nicIOAddr, #$0B		; RBCR1
		mov	w, #0			; MSB is always zero
		call	NICWrite

		; initialize RDMA to get source byte

:loop		clr	nicIOAddr		; CR
		mov	w, #%00100010		; Page0, abort DMA
		call	NICWrite

		mov	nicIOAddr, #$08		; RSAR0
		mov	w, nicCopySrcLSB
		call	NICWrite

		inc	nicIOAddr		; ($09) RSAR1
		mov	w, nicCopySrcMSB
		call	NICWrite

		mov	nicIOAddr, #$0A		; RBCR0
		mov	w, #1			; one-byte DMA
		call	NICWrite

		clr	nicIOAddr		; CR
		mov	w, #%00001010		; Page0, remote read
		call	NICWrite

		mov	nicIOAddr, #$10		; RDMA
		call	NICRead
		mov	nicCopyTemp, w		; store source byte temporarily

		; initialize RDMA to write byte to Destination
		mov	nicIOAddr, #$08		; RSAR0
		mov	w, nicCopyDestLSB
		call	NICWrite

		inc	nicIOAddr		; ($09) RSAR1
		mov	w, nicCopyDestMSB
		call	NICWrite

		inc	nicIOAddr		; ($0A) RBCR0
		mov	w, #1			; one-byte DMA
		call	NICWrite

		clr	nicIOAddr		; CR
		mov	w, #%00010010		; Page0, remote write
		call	NICWrite

		mov	nicIOAddr, #$10		; RDMA
		mov	w, nicCopyTemp
		call	NICWrite

		; increment source and Destination pointers
		inc	nicCopySrcLSB
		snz
		inc	nicCopySrcMSB
		inc	nicCopyDestLSB
		snz
		inc	nicCopyDestMSB

		; check if source page pointer hit receive buffer ceiling
		mov	w, nicCopySrcMSB
		xor	w, #RXBUF_END
		jnz	:here
		mov	nicCopySrcMSB, #RXBUF_START

		; loop as many times as there are bytes to copy
:here		decsz	nicCopyLenLSB
		jmp	:loop
		test	nicCopyLenMSB
		snz
		retp
		dec	nicCopyLenMSB
		jmp	:loop

; ******************************************************************************
_NICBufWrite
; Writes a byte to the SRAM buffer in the NIC
; INPUT:  {nicCopySrcMSB,nicCopySrcLSB} = address in buffer memory to write to
;	  w = byte to write
; OUTPUT: none
; ******************************************************************************
		bank	NIC_BANK

		mov	nicCopyTemp, w

		clr	nicIOAddr		; CR
		mov	w, #%00100010		; Page0, abort DMA

		mov	nicIOAddr, #$08		; RSAR0
		mov	w, nicCopySrcLSB
		call	NICWrite

		inc	nicIOAddr		; ($09) RSAR1
		mov	w, nicCopySrcMSB
		call	NICWrite

		inc	nicIOAddr		; ($0A) RBCR0
		mov	w, #1			; one-byte DMA
		call	NICWrite

		inc	nicIOAddr		; ($0B) RBCR1
		mov	w, #0			; MSB is always zero
		call	NICWrite

		clr	nicIOAddr		; CR
		mov	w, #%00010010		; Page0, remote write
		call	NICWrite

		mov	nicIOAddr, #$10		; RDMA
		mov	w, nicCopyTemp
		call	NICWrite

		retp

; ******************************************************************************
_NICBufRead
; Reads a byte from the SRAM buffer in the NIC
; INPUT:  {nicCopySrcMSB,nicCopySrcLSB} = address in buffer memory to read from
; OUTPUT: w = byte read
; ******************************************************************************
		bank	NIC_BANK

		clr	nicIOAddr		; CR
		mov	w, #%00100010		; Page0, abort DMA

		mov	nicIOAddr, #$08		; RSAR0
		mov	w, nicCopySrcLSB
		call	NICWrite

		inc	nicIOAddr		; ($09) RSAR1
		mov	w, nicCopySrcMSB
		call	NICWrite

		inc	nicIOAddr		; ($0A) RBCR0
		mov	w, #1			; one-byte DMA
		call	NICWrite

		inc	nicIOAddr		; ($0B) RBCR1
		mov	w, #0			; MSB is always zero
		call	NICWrite

		clr	nicIOAddr		; CR
		mov	w, #%00001010		; Page0, remote read
		call	NICWrite

		mov	nicIOAddr, #$10		; RDMA
		call	NICRead

		retp

; ******************************************************************************
_NICBufIPAddrWr
; Writes the source and destination IP addresses to the SRAM buffer in the NIC
; INPUT:  {nicCopySrcMSB,nicCopySrcLSB} = address in buffer memory to write to
;	  myIP3-0 = <source_IP>
;	  remoteIP3-0 = <destination_IP>
; OUTPUT: none
; ******************************************************************************
		bank	NIC_BANK

		clr	nicIOAddr		; CR
		mov	w, #%00100010		; Page0, abort DMA

		mov	nicIOAddr, #$08		; RSAR0
		mov	w, nicCopySrcLSB
		call	NICWrite

		inc	nicIOAddr		; ($09) RSAR1
		mov	w, nicCopySrcMSB
		call	NICWrite

		inc	nicIOAddr		; ($0A) RBCR0
		mov	w, #(4+4)		; 8-byte DMA
		call	NICWrite

		inc	nicIOAddr		; ($0B) RBCR1
		mov	w, #0			; MSB is always zero
		call	NICWrite

		clr	nicIOAddr		; CR
		mov	w, #%00010010		; Page0, remote write
		call	NICWrite

		mov	nicIOAddr, #$10		; RDMA

		; <source_IP>
		call	NICWriteSrcIP

		; <destination_IP>
		call	NICWriteDestIP

		retp

; ******************************************************************************
_NICWriteSrcIP
; Write Source IP address to NIC's buffer using remote DMA. NIC must be pre-
; initialized for this.
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		jnb	flags.GOT_IP_ADDR, :noIP	; use IP address at noIP if
		bank	IP_BANK				; we got none from DHCP
		mov	w, myIP3
		call	NICWrite
		bank	IP_BANK
		mov	w, myIP2
		call	NICWriteAgain
		mov	w, myIP1
		call	NICWriteAgain
		mov	w, myIP0
		call	NICWriteAgain
		retp

:noIP		mov	w, #0			; 0.0.0.0
		call	NICWrite
		mov	w, #0
		call	NICWriteAgain
		call	NICWriteAgain
		call	NICWriteAgain
		retp

; ******************************************************************************
_NICWriteDestIP
; Write Destination IP address to NIC's buffer using remote DMA. NIC must be
; pre-initialized for this.
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		bank	IP_BANK
		mov	w, remoteIP3
		call	NICWrite
		bank	IP_BANK
		mov	w, remoteIP2
		call	NICWriteAgain
		bank	IP_BANK
		mov	w, remoteIP1
		call	NICWriteAgain
		bank	IP_BANK
		mov	w, remoteIP0
		call	NICWriteAgain
		retp

; ******************************************************************************
_NICWriteSrcEth
; Write Source Ethernet address to NIC's buffer using remote DMA. NIC must be
; pre-initialized for this.
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		mov	w, #SX_ETH_ADDR0
		call	NICWrite
		mov	w, #SX_ETH_ADDR1
		call	NICWriteAgain
		mov	w, #SX_ETH_ADDR2
		call	NICWriteAgain
		mov	w, #SX_ETH_ADDR3
		call	NICWriteAgain
		mov	w, #SX_ETH_ADDR4
		call	NICWriteAgain
		mov	w, #SX_ETH_ADDR5
		call	NICWriteAgain
		retp

; ******************************************************************************
_NICWriteDestEth
; Write Destination Ethernet address to NIC's buffer using remote DMA. NIC must
; be pre-initialized for this.
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		mov	w, nicRemoteEth0
		call	NICWrite
		mov	w, nicRemoteEth1
		call	NICWriteAgain
		mov	w, nicRemoteEth2
		call	NICWriteAgain
		mov	w, nicRemoteEth3
		call	NICWriteAgain
		mov	w, nicRemoteEth4
		call	NICWriteAgain
		mov	w, nicRemoteEth5
		call	NICWriteAgain
		retp

; ******************************************************************************
_NICDMAInit
; Setup the NIC's RSAR and RBCR register in preparation for remote DMA.
; INPUT:  nicCurrPktPtr = beginning of packet
; OUTPUT: none
; ******************************************************************************
		bank	NIC_BANK

		clr	nicIOAddr		; CR
		mov	w, #%00100010		; Page0, abort DMA
		call	NICWrite

		; initialize DMA to <type> field in ethernet frame
		mov	nicIOAddr, #$08		; RSAR0
		mov	w, #(4+6+6)		; point to <type> field
		call	NICWrite
		inc	nicIOAddr		; ($09) RSAR1
		mov	w, nicCurrPktPtr
		call	NICWrite

		inc	nicIOAddr		; ($0A) RBCR0
		mov	w, #$FF
		call	NICWrite
		inc	nicIOAddr		; ($0B) RBCR1
		mov	w, #$0F
		call	NICWrite

		retp


	ORG	$400	; Page2

ARPSendResponse	jmp	_ARPSendResponse
ARPSendStPacket	jmp	_ARPSendStPacket
CheckIPDatagram	jmp	_CheckIPDatagram
CheckIPDestAddr	jmp	_CheckIPDestAddr
ICMPProcPktIn	jmp	_ICMPProcPktIn
Copy4Inc	jmp	_Copy4Inc

; ******************************************************************************
NICRead_2
; Shortform for calling NICRead(), which is in Page1 (This is in Page2)
; ******************************************************************************
		jmp	@NICRead

; ******************************************************************************
NICReadAgain_2
; Shortform for calling NICReadAgain(), which is in Page1 (This is in Page2)
; ******************************************************************************
		jmp	@NICReadAgain

; ******************************************************************************
NICPseudoRead_2
; Shortform for calling NICPseudoRead(), which is in Page1 (This is in Page2)
; ******************************************************************************
		jmp	@NICPseudoRead

; ******************************************************************************
NICPseudoRead6_2
; Shortform for calling NICPseudoRead(), which is in Page1 (This is in Page2)
; ******************************************************************************
		jmp	@NICPseudoRead6

; ******************************************************************************
NICWrite_2
; Shortform for calling NICWrite(), which is in Page1 (This is in Page2)
; ******************************************************************************
		jmp	@NICWrite

; ******************************************************************************
NICWriteAgain_2
; Shortform for calling NICWriteAgain(), which is in Page1 (This is in Page2)
; ******************************************************************************
		jmp	@NICWriteAgain

; ******************************************************************************
NICDumpRxFrame_2
; Shortform for calling NICDumpRxFrame(), which is in Page1 (This is in Page2)
; ******************************************************************************
		jmp	@NICDumpRxFrame

; ******************************************************************************
ARPCheckCache
; Checks if remote(destination) host IP address is in cache. If so, update
; packet Ethernet address in NIC with cache entry, else send ARP request.
; INPUT:  remoteIP0-3, host1IP0-3
; OUTPUT: none
; ******************************************************************************
		jnb	flags3.ARP_STL_TX, :cacheGo	; do not check cache
							; if stalled packet
							; to be txed
		call	ARPUpdateEthAddr	; update stalled packet's ethernet address
		mov	w, #%11111000
		and	flags3, w		; reset ARP
		retp

:cacheGo	; check if remote host IP is in cache
		bank	IP_BANK
		mov	w, remoteIP0
		bank	ARP_BANK
		xor	w, host1IP0
		jnz	:cacheNoMatch		; no match

		mov	w, host1IP1
		bank	IP_BANK
		xor	w, remoteIP1
		jnz	:cacheNoMatch		; no match

		mov	w, remoteIP2
		bank	ARP_BANK
		xor	w, host1IP2
		jnz	:cacheNoMatch		; no match

		mov	w, host1IP3
		bank	IP_BANK
		xor	w, remoteIP3
		jz	:cacheMatch		; match! remoteIP0-3 = host1IP0-3

:cacheNoMatch	setb	flags3.ARP_REQ_SENT	; indicate an ARP request is sent
		jmp	ARPSendRequest		; do an ARP request to get the
						; remote Eth address

:cacheMatch	mov	w, #%11111000
		and	flags3, w		; reset ARP
		jmp	ARPUpdateEthAddr	; update remote Eth address of
						; pending packet

; ******************************************************************************
ARPUpdateEthAddr
; Updates Eth Address of pending packet to be txed in NIC's buffer with that in
; ARP cache
; INPUT:  host1Eth0-5, stPktTxBufStart
; OUTPUT: none
; ******************************************************************************
		bank	NIC_BANK

		clr	nicIOAddr		; CR
		mov	w, #%00100010		; Page0, abort DMA
		call	NICWrite_2

		mov	nicIOAddr, #$08		; RSAR0
		mov	w, #0
		call	NICWrite_2

		inc	nicIOAddr		; ($09) RSAR1
		bank	ARP_BANK
		mov	w, stPktTxBufStart	; store page no of stalled pkt
		call	NICWrite_2

		bank	NIC_BANK
		inc	nicIOAddr		; ($0A) RBCR0
		mov	w, #(6+6)		; 12-byte DMA
		call	NICWrite_2

		inc	nicIOAddr		; ($0B) RBCR1
		mov	w, #0			; MSB is always zero
		call	NICWrite_2

		clr	nicIOAddr		; CR
		mov	w, #%00010010		; Page0, remote write
		call	NICWrite_2

		mov	nicIOAddr, #$10		; RDMA

		; <destination_Eth>
		bank	ARP_BANK
		mov	w, host1Eth0
		call	NICWrite_2
		bank	ARP_BANK
		mov	w, host1Eth1
		call	NICWriteAgain_2
		mov	w, host1Eth2
		call	NICWriteAgain_2
		mov	w, host1Eth3
		call	NICWriteAgain_2
		mov	w, host1Eth4
		call	NICWriteAgain_2
		mov	w, host1Eth5
		jmp	NICWriteAgain_2

; ******************************************************************************
ARPCheckIfIs
; Checks received packet to see if it is ARP.
; Sends an ARP response if its a request.
; Updates cache if it's an ARP response.
; INPUT:  nicCurrPktPtr = points to beginning of received packet
; OUTPUT: remoteIP0-3 (:rcvdARPRequest), host1Eth0-5 (:rcvdARPResponse)
; ******************************************************************************
		clrb	flags.RX_IS_ARP

		call	@NICDMAInit

		clr	nicIOAddr		; CR
		mov	w, #%00001010		; Page0, remote read
		call	NICWrite_2

		mov	nicIOAddr, #$10		; RDMA

		; check if ethernet <type> field
		; contains ARP identifier (0x0806)
		call	NICRead_2
		xor	w, #$08
		jnz	:outtaHere
		call	NICReadAgain_2
		xor	w, #$06
		jnz	:outtaHere

		setb	flags.RX_IS_ARP		; yes, it's ARP, indicate that for
						; main loop

		; ignore <hardware_type>,<protocol_type>,<HLEN>,<PLEN>
		call	NICPseudoRead6_2

		; checks if the ARP packet received is a request or a response
		call	NICReadAgain_2
		xor	w, #$00
		jnz	:outtaHere		; not ARP at all

		call	NICReadAgain_2
		mov	globTemp1, w		; store ARP opcode
		xor	w, #$01			; check if ARP Request (0x0001)
		jz	:rcvdARPRequest		; yes, process ARP request
		mov	w, globTemp1
		xor	w, #$02			; check if ARP Response (0x0002)
		jz	:rcvdARPResponse	; yes, process ARP response
		jmp	:outtaHere		; no, not ARP at all

		; ignore <sender_HA>
:rcvdARPRequest	call	NICPseudoRead6_2

		; record the sender's IP addr (<sender_IP>)
		; will be used to reply to sender
:senderIP	bank	IP_BANK
		call	NICReadAgain_2
		mov	remoteIP3, w
		call	NICReadAgain_2
		mov	remoteIP2, w
		call	NICReadAgain_2
		mov	remoteIP1, w
		call	NICReadAgain_2
		mov	remoteIP0, w

		; ignore <target_HA>
:targetHA	call	NICPseudoRead6_2

		; check if <target_IP> is me
		mov	fsr, #myIP3
		call	ARPCompare4
		jnz	:outtaHere

		; so it's for me!
		call	ARPSendResponse
		jmp	NICDumpRxFrame_2	; we're done with this packet, dump it

:rcvdARPResponse
		; <sender_HA>
		; record sender's Eth address in ARP cache
		mov	fsr, #host1Eth0
		mov	globTemp1, #6
:ethLoop	call	NICReadAgain_2
		mov	indf, w
		inc	fsr
		decsz	globTemp1
		jmp	:ethLoop

		; <sender_IP>
		; check if sender's IP corrresponds to IP address in ARP cache
		mov	fsr, #host1IP3
		call	ARPCompare4
		jz	:ipAddrResolved

		; whoops, sender doesn't correspond to who we're trying to resolve!
		;   if we're waiting for an ARP response (no problem here)
		jb	flags3.ARP_REQ_SENT, :outtaHere
		;   otherwise, we need to invalidate the now corrupted ARP cache
		clr	host1IP3
		clr	host1IP1
		clr	host1IP2
		clr	host1IP0
		jmp	:outtaHere

:ipAddrResolved	setb	flags3.ARP_RSP_RCVD	; indicate we got a successfull ARP response
		setb	flags3.ARP_STL_TX	; transmit the stalled packet now

:outtaHere	snb	flags.RX_IS_ARP		; check if packet was ARP
		call	NICDumpRxFrame_2	; if it was ARP, we dump it; otherwise, don't touch it
		retp

; ******************************************************************************
ARPCompare4
; Compares Data from NICReadAgain() against a 4-byte word store consequtively
; in the SX's Data memory
; INPUT:  fsr = points to beginning of 4-byte word to compare against
; OUTPUT: z: 1 = match, 0 = not match
; ******************************************************************************
		mov	globTemp1, #4
:loop		call	NICReadAgain_2
		xor	w, indf
		sz
		retp				; mis-match
		inc	fsr
		decsz	globTemp1
		jmp	:loop
		stz
		retp

; ******************************************************************************
ARPSendCommon1
; Helper function for ARPSendRequest and ARPSendResponse
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		; <type> = 0x0806
		mov	w, #$08
		call	NICWriteAgain_2
		mov	w, #$06
		call	NICWriteAgain_2

		; <hardware_type> = 0x0001
		mov	w, #$00
		call	NICWriteAgain_2
		mov	w, #$01
		call	NICWriteAgain_2

		; <protocol_type> = 0x0800
		mov	w, #$08
		call	NICWriteAgain_2
		mov	w, #$00
		call	NICWriteAgain_2

		; <HLEN> = 0x06
		mov	w, #$06
		call	NICWriteAgain_2

		; <PLEN> = 0x04
		mov	w, #$04
		jmp	NICWriteAgain_2

; ******************************************************************************
ARPSendCommon2
; Helper function for ARPSendRequest and ARPSendResponse
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		; <sender_HA>
		call	@NICWriteSrcEth

		; <sender_IP>
		call	@NICWriteSrcIP

		; <target_HA>
		call	@NICWriteDestEth

		; <target_IP>
		call	@NICWriteDestIP

		; whew! the ethernet frame is now complete, get ready to send ..
		bank	NIC_BANK		; NICWriteDestIP changes bank
		clr	nicIOAddr		; CR
		mov	w, #%00100010		; Page0, abort DMA
		call	NICWrite_2

		mov	nicIOAddr, #$05		; TBCR0
		mov	w, #$40			; min ethernet packet size
		call	NICWrite_2
		inc	nicIOAddr		; ($06) TBCR1
		mov	w, #$00
		call	NICWrite_2

		clr	nicIOAddr		; CR
		mov	w, #%00000110		; transmit
		jmp	NICWrite_2

; ******************************************************************************
ARPSendRequest
; Stores remote IP address for which pending packet is intended in ARP cache and
; sends an ARP Request
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		bank	IP_BANK
		mov	w, remoteIP3		; store IP address of target in ARP cache
		bank	ARP_BANK
		mov	host1IP3, w
		bank	IP_BANK
		mov	w, remoteIP2
		bank	ARP_BANK
		mov	host1IP2, w
		bank	IP_BANK
		mov	w, remoteIP1
		bank	ARP_BANK
		mov	host1IP1, w
		bank	IP_BANK
		mov	w, remoteIP0
		bank	ARP_BANK
		mov	host1IP0, w

		bank	NIC_BANK
		mov	w, #$FF
		mov	nicRemoteEth0, w	; setup broadcast Ethernet address
		mov	nicRemoteEth1, w
		mov	nicRemoteEth2, w
		mov	nicRemoteEth3, w
		mov	nicRemoteEth4, w
		mov	nicRemoteEth5, w

		mov	w, #TXBUF4_START	; point to ARP transmit buffer
		call	@NICInitTxFrame

		call	ARPSendCommon1

		; <operation> = 0x0001 , ARP request opcode
		mov	w, #$00
		call	NICWriteAgain_2
		mov	w, #$01
		call	NICWriteAgain_2

		call	ARPSendCommon2

		bank	TIMER_BANK		; initialise the APR timeout timer
		clr	arpTimerMSB
		clr	arpTimerLSB

		retp

; ******************************************************************************
_ARPSendResponse
; Send an ARP Response in reply to a ARP request.
; INPUT: none
; OUTPUT: none
; ******************************************************************************
		mov	w, #TXBUF4_START	; point to ARP transmit buffer
		call	@NICInitTxFrame

		call	ARPSendCommon1

		; <operation> = 0x0002
		mov	w, #$00
		call	NICWriteAgain_2
		mov	w, #$02
		call	NICWriteAgain_2

		jmp	ARPSendCommon2

; ******************************************************************************
_ARPSendStPacket
; Sends the ARP stalled packet if any and also check if a timeout on waiting for
; an ARP response occured. If timeout, clear ARP cache
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		sb	flags3.ARP_REQ_SENT	; check if we are waiting for
						; an ARP response
		retp				; no, nothing to Do here

		; if no ARP response rcvd, check if timed out on waiting for it
		jnb	flags3.ARP_STL_TX, :checkARPTimeout

		; yes we have rcvd a response we've been waiting for
		; now we can send the stalled packet
		mov	w, #%11111000
		and	flags3,	w		; reset ARP

		; re-initialize the NIC's TPSR
		bank	NIC_BANK
		clr	nicIOAddr		; CR
:wait		call	NICRead_2
		jb	wreg.2,	:wait		; wait for prior transmission
						; to complete
		mov	w, #%00100010		; Page0, abort DMA
		call	NICWrite_2
		mov	nicIOAddr, #$04		; TPSR
		bank	ARP_BANK
		mov	w, stPktTxBufStart	; load stalled packet's
						; address pointer
		call	NICWrite_2

		; read the NIC's transmit buffer to find out the IP <length>
		; so that we can re-initialize the NIC's TBCR
		bank	ARP_BANK
		mov	w, stPktTxBufStart
		bank	NIC_BANK
		mov	nicCopySrcMSB, w
		mov	nicCopySrcLSB, #(6+6+2+2)	; IP <length> (MSB)
		call	@NICBufRead
		bank	IP_BANK
		mov	ipLengthMSB, w
		bank	NIC_BANK
		inc	nicCopySrcLSB		; IP <length> (LSB)
		call	@NICBufRead
		bank	IP_BANK
		mov	ipLengthLSB, w
		jmp	@NICSendTxFrame		; transmit the stalled ethernet frame

:checkARPTimeout
		bank	TIMER_BANK
		csae	arpTimerMSB, #ARP_TIMEOUT	; has the timer expired?
		retp					; no, just return

		mov	w, #%11111000
		and	flags3,	w			; yes, reset ARP flags
		; Clear the ARP cache, since it acted as a temporary storage
		; of the requested IP address. If we Do not clear the cache now,
		; the next re-transmit routine will find a false match in the
		; ARP cache.
		bank	ARP_BANK
		clr	host1IP3
		clr	host1IP2
		clr	host1IP1
		clr	host1IP0
		retp

; ******************************************************************************
_CheckIPDatagram
; Checks to see if received ethernet frame contains an IP Datagram. If not, the
; frame	will be discarded since this stack Doesn't Deal with any other kinds of
; Data-link layer protocol.
; INPUT:  nicCurrPktPtr = points to beginning of received packet
; OUTPUT: none
; ******************************************************************************
		clrb	flags.RX_IS_IP_BCST	; clear broadcast IP indication

CHKIP_MASK = ~((1<<RX_IS_ICMP)|(1<<RX_IS_UDP)|(1<<RX_IS_TCP)|(1<<RX_IS_IP_BCST))
		mov	w, #CHKIP_MASK
		and	flags, w

		call	@NICDMAInit

		clr	nicIOAddr		; CR
		mov	w, #%00001010		; Page0, remote read
		call	NICWrite_2

		mov	nicIOAddr, #$10		; RDMA

		; check if ethernet <type> field contains IP identifier (0x0800)
		call	NICRead_2
		xor	w, #$08
		jnz	:outtaHere
		call	NICReadAgain_2
		xor	w, #$00
		jnz	:outtaHere

		; check <version> and <HLEN>
		call	NICReadAgain_2
		xor	w, #$45			; version = 4, header length = 5
		jnz	:outtaHere

		; ignore <service_type>
		call	NICPseudoRead_2

		; record <total_length>
		call	NICReadAgain_2
		bank	IP_BANK
		mov	ipLengthMSB, w
		call	NICReadAgain_2
		mov	ipLengthLSB, w
		; adjust {ipLengthMSB,ipLengthLSB} to reflect number of
		; Data bytes
		sub	ipLengthLSB, #20
		sc
		dec	ipLengthMSB

		; ignore <identification>
	REPT	2
		call	NICPseudoRead_2
	ENDR

		; check against IP packet fragmentation
		call	NICReadAgain_2
		jb	wreg.5,	:outtaHere	; <flags>
		call	NICReadAgain_2
		xor	w, #0			; <fragment_offset>
		jnz	:outtaHere

		; ignore <time_to_live>
		call	NICPseudoRead_2

		; record <protocol>
		call	NICReadAgain_2
		mov	ipProtocol, w

		; ignore <header_checksum>
	REPT	2
		call	NICPseudoRead_2
	ENDR

		; record <source_IP>
:srcIP		bank	IP_BANK
		call	NICReadAgain_2
		mov	remoteIP3, w
		call	NICReadAgain_2
		mov	remoteIP2, w
		call	NICReadAgain_2
		mov	remoteIP1, w
		call	NICReadAgain_2
		mov	remoteIP0, w

		; check <destination_IP>
:destIP		clr	globTemp2
		mov	w, myIP3
		call	CheckIPDestAddr
		jnz	:outtaHere
		mov	w, myIP2
		call	CheckIPDestAddr
		jnz	:outtaHere
		mov	w, myIP1
		call	CheckIPDestAddr
		jnz	:outtaHere
		mov	w, myIP0
		call	CheckIPDestAddr
		jnz	:outtaHere
		xor	globTemp2, #4
		snz
		setb	flags.RX_IS_IP_BCST	; IP broadcast addr Detected

		; ok! Determine which higher-level protocol
		mov	w, #1			; ICMP
		xor	w, ipProtocol
		snz
		setb	flags.RX_IS_ICMP
		mov	w, #17			; UDP
		xor	w, ipProtocol
		snz
		setb	flags.RX_IS_UDP
		mov	w, #6			; TCP
		xor	w, ipProtocol
		snz
		setb	flags.RX_IS_TCP
		retp

:outtaHere	jmp	NICDumpRxFrame_2	; if it ain't IP, forget it!

; ******************************************************************************
_CheckIPDestAddr
; Helper function for CheckIPDatagram
; Check for a match of the IP <destination_IP> field
; INPUT:  w = byte of IP to check against
; OUTPUT: z = set if match
;	  globTemp2 = incremented if matched against 0xFF
; ******************************************************************************
		mov	globTemp1, w
		call	NICReadAgain_2
		xor	globTemp1, w
		snz
		retp
		xor	w, #$FF			; IP broadcast addr
		sz
		retp
		inc	globTemp2
		stz
		retp

; ******************************************************************************
_ICMPProcPktIn
; Process ICMP message. Only Echo Request ICMP messages are handled. All others
; are ignored. If Echo Request message is Detected, this will generate the echo
; reply.
; INPUT:  nicCurrPktPtr = points to beginning of received packet
; OUTPUT: none
; ******************************************************************************
		; check <type>
		call	NICReadAgain_2
		xor	w, #$08				; echo-request
		jnz	:outtaHere

		bank	IP_BANK
		add	ipLengthLSB, #(2+20)
		snc
		inc	ipLengthMSB

		bank	NIC_BANK
		mov	nicCopySrcMSB, nicCurrPktPtr	; point to receive
							; buffer
		mov	nicCopySrcLSB, #(4+6+6)		; don't copy NIC header
							; , eth src & Destn

		mov	nicCopyDestMSB, #TXBUF1_START	; point to transmit
							; buffer
		mov	nicCopyDestLSB, #(6+6)

		bank	IP_BANK
		mov	w, ipLengthMSB
		bank	NIC_BANK
		mov	nicCopyLenMSB, w
		bank	IP_BANK
		mov	w, ipLengthLSB
		bank	NIC_BANK
		mov	nicCopyLenLSB, w

		mov	w, #TXBUF1_START	; point to transmit buffer

		bank	ARP_BANK
		mov	stPktTxBufStart, w	; Store ICMP packet start address
		bank	NIC_BANK

		call	@NICInitTxFrame
		call	@NICBufCopy

		; change ICMP <type> to echo reply (0)
		mov	nicCopySrcMSB, #TXBUF1_START	; point to transmit
							; buffer
		mov	nicCopySrcLSB, #(6+6+2+20)	; point to ICMP <type>
		mov	w, #$00
		call	@NICBufWrite

		; generate ICMP <checksum>
		mov	nicCopySrcMSB, #TXBUF1_START	; point to transmit
							; buffer
		bank	IP_BANK
		mov	w, ipLengthMSB
		bank	NIC_BANK
		mov	nicCopyLenMSB, w
		bank	IP_BANK
		mov	w, ipLengthLSB
		bank	NIC_BANK
		mov	nicCopyLenLSB, w
		sub	nicCopyLenLSB, #(2+20+4)
		sc
		dec	nicCopyLenMSB
		call	@ICMPGenCheckSum

		; adjust IP <source_IP> and <destination_IP>
		mov	nicCopySrcMSB, #TXBUF1_START	; point to transmit
							; buffer
		mov	nicCopySrcLSB, #(6+6+2+12)	; point to IP <source_IP>
		call	@NICBufIPAddrWr

		bank	IP_BANK
		sub	ipLengthLSB, #2
		sc
		dec	ipLengthMSB
		call	@NICSendTxFrame

:outtaHere	jmp	NICDumpRxFrame_2

; ******************************************************************************
_Copy4Inc
; Copies 4 variables incrementally by copying from var pointed to by globTemp3
; to variable pointed to by globTemp1
; INPUT:  globTemp1,3 points to variables
; OUTPUT: none
; ******************************************************************************
		_bank	IP_BANK
		mov	counter1, #4	; load the counter
:loop
		mov	fsr, globTemp3	; move tcp pointer to fsr
		mov	w, indf		; move tcp var in w
		mov	globTemp2, w	; store tcp var in glob
		mov	fsr, globTemp1	; move tcb pointer to fsr
		mov	indf, globTemp2	; overwrite tcb var with tcp var
		inc	globTemp1	; increment pointer
		inc	globTemp3	; increment pointer

		_bank	IP_BANK		; check loop counter until finished
		dec	counter1
		sz
		jmp	:loop

		retp

	IF	SMTP
SMTPTEXT_DATA	=	$
_SMTPTEXT_DATA	dw	'DATA',$0D,$0A
	ENDIF


	ORG	$600	; Page3

; ******************************************************************************
NICWrite_3
; Shortform for calling NICWrite(), which is in Page1 (This is in Page3)
; ******************************************************************************
		jmp	@NICWrite

; ******************************************************************************
NICWriteAgain_3
; Shortform for calling NICWriteAgain(), which is in Page1 (This is in Page3)
; ******************************************************************************
		jmp	@NICWriteAgain

; ******************************************************************************
ICMPGenCheckSum
; Goes through the ICMP message stored in the NIC's SRAM buffer and computes
; the ICMP message checksum.
; INPUT:  nicCopySrcMSB = transmit buffer page
;	  {nicCopyLenMSB,nicCopyLenLSB} = (length of ICMP message - 4)
; OUTPUT: {ipCheckSumMSB,ipCheckSumLSB} = new checksum value
; ******************************************************************************
		call	IPCheckSumInit

		bank	NIC_BANK

		mov	nicCopyTemp, nicCopySrcMSB

		clr	nicIOAddr		; CR
		mov	w, #%00100010		; Page0, abort DMA

		mov	nicIOAddr, #$08		; RSAR0
		mov	w, #(6+6+2+20+4)	; point to ICMP <identifier>
		call	NICWrite_3

		inc	nicIOAddr		; ($09) RSAR1
		mov	w, nicCopySrcMSB
		call	NICWrite_3

		inc	nicIOAddr		; ($0A) RBCR0
		mov	w, nicCopyLenLSB
		call	NICWrite_3

		inc	nicIOAddr		; ($0B) RBCR1
		mov	w, nicCopyLenMSB
		call	NICWrite_3

		clr	nicIOAddr		; CR
		mov	w, #%00001010		; Page0, remote read
		call	NICWrite_3

		mov	nicIOAddr, #$10		; RDMA

		; configure Data bus for input
		_mode	DIR_W
		mov	w, #$FF			; input
		mov	!NIC_DATA_PORT,	w

		; put addr out on addr bus
		mov	w, NIC_CTRL_PORT
		and	w, #%11100000
		or	w, nicIOAddr
		mov	NIC_CTRL_PORT, w

		inc	nicCopyLenMSB		; in order to loop easier later

:loop		call	@NICReadAgain
		call	IPCheckSumAcc
		bank	NIC_BANK
		decsz	nicCopyLenLSB
		jmp	:loop
		decsz	nicCopyLenMSB
		jmp	:loop

		mov	nicCopySrcMSB, nicCopyTemp
		mov	nicCopySrcLSB, #(6+6+2+20+2)
		bank	IP_BANK
		mov	w, /ipCheckSumMSB
		call	@NICBufWrite
		inc	nicCopySrcLSB
		bank	IP_BANK
		mov	w, /ipCheckSumLSB
		call	@NICBufWrite
		retp

; ******************************************************************************
IPCheckSumInit
; Initializes the IP checksum routine.
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		bank	IP_BANK
		clr	ipCheckSumMSB
		clr	ipCheckSumLSB
		clrb	flags.IP_CHKSUM_LSB	; next byte is MSB
		retp

; ******************************************************************************
IPCheckSumAcc
; Accumulate the IP checksum value by adding the next 16-bit number
; IP header checksum (also used to compute ICMP checksum) is computed by Doing
; the one's complement of the one's complement sum of the 16-bit numbers in the
; header.
; INPUT:  w = byte to accumulate
;	  flags.IP_CHKSUM_LSB = set if processing LSB, clear if processing MSB
; OUTPUT: {ipCheckSumMSB,ipCheckSumLSB} = new checksum value
; ******************************************************************************
		bank	IP_BANK

		jnb	flags.IP_CHKSUM_LSB, :msb	; are we processing an MSB?

:lsb		add	ipCheckSumLSB, w	; add it to the checksum
		sc				; was there a carry?
		jmp	:done
		inc	ipCheckSumMSB		; yes
		snz
		inc	ipCheckSumLSB
		jmp	:done

:msb		add	ipCheckSumMSB, w	; add it to the checksum
		sc				; was there a carry?
		jmp	:done
		inc	ipCheckSumLSB		; yes, this time it is added
						; to the LSB
		snz
		inc	ipCheckSumMSB

:done		xor	flags, #(1<<IP_CHKSUM_LSB)
		retp

; ******************************************************************************
IPGenCheckSum
; Generate the IP header checksum
; INPUT:  {ipLengthMSB,ipLengthLSB} = IP <total_length>
;	  {ipIdentMSB,ipIdentLSB} = IP <identification>
;	  ipProtocol = 1(ICMP)/ 6(TCP)/ 17(UDP)
; OUTPUT: {ipCheckSumMSB,ipCheckSumLSB} = new checksum value
; ******************************************************************************
		bank	IP_BANK

		call	IPCheckSumInit

		mov	w, #$45			; Version 4, 5x 32 bit words in IP header
		call	IPCheckSumAcc
		mov	w, #$00
		call	IPCheckSumAcc

		mov	w, ipLengthMSB
		call	IPCheckSumAcc
		mov	w, ipLengthLSB
		call	IPCheckSumAcc

		mov	w, ipIdentMSB
		call	IPCheckSumAcc
		mov	w, ipIdentLSB
		call	IPCheckSumAcc

		mov	w, #%01000000		; don't fragment
		call	IPCheckSumAcc
		mov	w, #0
		call	IPCheckSumAcc

		mov	w, #IP_TTL
		call	IPCheckSumAcc
		mov	w, ipProtocol
		call	IPCheckSumAcc

		jnb	flags.GOT_IP_ADDR, :remoteIP	; IP = 0.0.0.0 if no IP

		mov	w, myIP3
		call	IPCheckSumAcc
		mov	w, myIP2
		call	IPCheckSumAcc
		mov	w, myIP1
		call	IPCheckSumAcc
		mov	w, myIP0
		call	IPCheckSumAcc

		; check if this is a tcp packet being constructed and use
		; the remoteIP address of the correct socket.
		; we can only respond to hosts Defined in the tcp sockets
		; foreign incoming packets are not responded to by sending
		; a reset since this will need another TCB, they are flat-out
		; ignored. The foreign remote host will eventually give up .
:remoteIP	mov	w, ipProtocol
		xor	w, #6		; is it tcp?
		jnz	:skipSocketCpy	; no, Don't use remoteIP in tcp sockets

		; check which is the current tcp connection
		mov	globTemp1, #remoteIP3		; set source pointer
		mov	w, #sock1RemoteIP3
		sb	flags2.TCP_SOCK
		mov	globTemp3, w
		mov	w, #sock2RemoteIP3	; destination pointer
		snb	flags2.TCP_SOCK
		mov	globTemp3, w

		call	@Copy4Inc	; copy sockRemoteIP into remoteIP

:skipSocketCpy	_bank	IP_BANK		; accumulate checksum with remoteIP
		mov	w, remoteIP3
		call	IPCheckSumAcc
		mov	w, remoteIP2
		call	IPCheckSumAcc
		mov	w, remoteIP1
		call	IPCheckSumAcc
		mov	w, remoteIP0
		call	IPCheckSumAcc

		retp

; ******************************************************************************
IPStartPktOut
; Starts an outgoing IP packet by constructing the IP packet header
; INPUT:  {ipLengthMSB,ipLengthLSB} = IP <total_length>
;	  {ipIdentMSB,ipIdentLSB} = IP <identification>
;	  ipProtocol = 1(ICMP)/ 6(TCP)/ 17(UDP)
;	  {ipCheckSumMSB,ipCheckSumLSB} = IP <header_checksum>
; OUTPUT: none
; ******************************************************************************
		bank	IP_BANK
		mov	w, #6			; is it tcp?
		xor	w, ipProtocol
		jz	:useTcpBuf		; yes

		mov	w, #TXBUF1_START	; no, Default tx buffer is TXBUF1
		jmp	:contIpStartP

:useTcpBuf	sb	flags2.TCP_SOCK		; point to correct tcp conn.
						; tx buffer
		mov	w, #TXBUF2_START	; tcp connection1 tx buffer
		snb	flags2.TCP_SOCK
		mov	w, #TXBUF3_START	; tcp connection2 tx buffer

:contIpStartP	bank	ARP_BANK
		sb	flags3.ARP_REQ_SENT	; check if a prev packet
						; is stalled
		mov	stPktTxBufStart, w	; no, store new address pointer
						; to new packet
		call	@NICInitTxFrame

		; <type> = 0x0800
		mov	w, #$08
		call	NICWrite_3
		mov	w, #$00
		call	NICWriteAgain_3

		; <version> = 4, <HLEN> = 5
		mov	w, #$45
		call	NICWriteAgain_3

		; <service_type>
		mov	w, #$00			; normal precedence, D=T=R=0
		call	NICWriteAgain_3

		; <total_length>
		bank	IP_BANK
		mov	w, ipLengthMSB
		call	NICWriteAgain_3
		mov	w, ipLengthLSB
		call	NICWriteAgain_3

		; <identification>
		mov	w, ipIdentMSB
		call	NICWriteAgain_3
		mov	w, ipIdentLSB
		call	NICWriteAgain_3

		; <flags>, <fragment_offset>
		mov	w, #%01000000		; do not fragment
		call	NICWriteAgain_3
		mov	w, #0			; offset = 0
		call	NICWriteAgain_3

		; <time_to_live>
		mov	w, #IP_TTL
		call	NICWriteAgain_3

		; <protocol>
		mov	w, ipProtocol
		call	NICWriteAgain_3

		; <header_checksum>
		mov	w, /ipCheckSumMSB
		call	NICWriteAgain_3
		mov	w, /ipCheckSumLSB
		call	NICWriteAgain_3

		; <source_IP>
		call	@NICWriteSrcIP

		; <destination_IP>
		call	@NICWriteDestIP

		retp

; ******************************************************************************
TCPAppPassiveOpen
; Do a passive open. i.e. listen for connections on a given port.
; [TCP API Function]
; INPUT:  {tcb1LocalPortMSB,tcb1LocalPortLSB} = TCP port to listen on
; OUTPUT: none
; ******************************************************************************
		_bank	TCP_BANK
		mov	tcp2State, #TCP_ST_LISTEN
		clr	tcp2UnAckMSB
		clr	tcp2UnAckLSB
		retp

; ******************************************************************************
TCPAppActiveOpen
; Do a active open. i.e. initiate a connect to a remote TCP.
; [TCP API Function]
; INPUT:  {remoteIP0-3} = Destination IP addr
;	  {tcbLocalPortMSB,tcbLocalPortLSB} = local TCP port
;	  {tcbRemotePortMSB,tcbRemotePortLSB} = remote TCP port
; OUTPUT: none
; ******************************************************************************
		_bank	TCP_BANK
		clr	tcp1UnAckMSB
		clr	tcp1UnAckLSB
		mov	tcp1State, #TCP_ST_SYNSENT
		bank	TCB1_BANK
		mov	tcb1Flags, #(1<<TCP_FLAG_SYN)
		jmp	@TCPSendSyn

; ******************************************************************************
TCPAppClose
; Force the current connection to close
; [TCP API Function]
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		; check which tcp connection to close
		_bank	TCP_BANK
		jb	flags2.TCP_SOCK, :finwaitSock2

		; close tcp connection1
		mov	tcp1State, #TCP_ST_FINWAIT1
		clr	tcp1UnAckMSB
		clr	tcp1UnAckLSB
		jmp	:contAppClose

		; close tcp connection2
:finwaitSock2	mov	tcp2State, #TCP_ST_FINWAIT1
		clr	tcp2UnAckMSB
		clr	tcp2UnAckLSB

		; common tcp close code
:contAppClose	mov	globTemp1, #(tcb1Flags-TCB1_BANK)
		call	@SetTCBPointer
		mov	indf, #((1<<TCP_FLAG_FIN)|(1<<TCP_FLAG_ACK))
		jmp	@TCPSendEmptyPkt

; ******************************************************************************
TCPProcPktIn
; Process a received TCP packet. This function implements the TCP state machine
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		call	@TCPRxHeader		; receive the header
		snz				; is the packet OK?
		retp				; no, return

		; check if incoming packet is for tcp conn. 1 or 2
		bank	TCP_BANK
		jb	flags2.TCP_SOCK, :isTcp2

		; check the special states for tcp connection1
		cje	tcp1State, #TCP_ST_CLOSED, :CLOSED
		cje	tcp1State, #TCP_ST_LISTEN, :LISTEN
		cje	tcp1State, #TCP_ST_SYNSENT, :SYNSENT
		cje	tcp1State, #TCP_ST_FINWAIT1, :FINWAIT1
		cje	tcp1State, #TCP_ST_FINWAIT2, :FINWAIT2
		cje	tcp1State, #TCP_ST_LASTACK, :LASTACK
		jmp	:contProc

		; check the special states for tcp connection2
:isTcp2		cje	tcp2State, #TCP_ST_CLOSED, :CLOSED
		cje	tcp2State, #TCP_ST_LISTEN, :LISTEN
		cje	tcp2State, #TCP_ST_SYNSENT, :SYNSENT
		cje	tcp2State, #TCP_ST_FINWAIT1, :FINWAIT1
		cje	tcp2State, #TCP_ST_FINWAIT2, :FINWAIT2
		cje	tcp2State, #TCP_ST_LASTACK, :LASTACK

:contProc	call	@TCPCmpNxtSeq		; check if RCV.NXT == SEG.SEQ
		sz				; are they equal?
		jmp	@TCPSendAck		; no, send an ACK and Drop packet

		; check the flags
		mov	globTemp1, #(tcb1Flags-TCB1_BANK)
		call	@SetTCBPointer		; point to tcp conn's TCB

		snb	indf.TCP_FLAG_RST	; is the reset flag set?
		jmp	:gotoClosed		; yes, Drop packet and
						; close the connection

		snb	indf.TCP_FLAG_SYN	; is the SYN bit set?
		jmp	@TCPSendReset		; yes, Drop the packet and
						; send a reset

		sb	indf.TCP_FLAG_ACK	; is the ACK bit set?
		jmp	@NICDumpRxFrame		; no, Drop the packet

		; we only accept ACKs of complete packets.
		; Assume the ACK is for our last packet
		mov	w, #TCP_ST_ESTABED	; indicate tcp conn established
		bank	TCP_BANK
		sb	flags2.TCP_SOCK		; indicate tcp state to correct
		mov	tcp1State, w		; tcp conn. state variable
		snb	flags2.TCP_SOCK
		mov	tcp2State, w

		; if we were in Syn-received then need to send an ACK
		; check if for tcp1 or 2
		jb	flags2.TCP_SOCK, :tcp2AckCheck

		;tcp1
		test	tcp1UnAckLSB
		sz
		jmp	:outstanding
		test	tcp1UnAckMSB
		snz
		jmp	:noOutstanding
		jmp	:outstanding

		; tcp2
:tcp2AckCheck	test	tcp2UnAckLSB
		sz
		jmp	:outstanding
		test	tcp2UnAckMSB
		snz
		jmp	:noOutstanding
		jmp	:outstanding

:outstanding	call	@TCPAppTxDone		; tell the application it was ACKed
		_bank	TCP_BANK		; _bank cus we called TCPAppTxDone priorly

		; check if for tcp1 or 2
		jb	flags2.TCP_SOCK, :clrTcpUnack2
		clr	tcp1UnAckLSB		; there should be no Data outstanding now
		clr	tcp1UnAckMSB
		jmp	:noOutstanding

:clrTcpUnack2	clr	tcp2UnAckLSB		; there should be no Data outstanding now
		clr	tcp2UnAckMSB

:noOutstanding	;	Does the packet contain Data? (Determine this from the length)
		test	tcpLengthMSB
		jnz	:packetHasData		; MSB is not zero
		test	tcpLengthLSB		; MSB is zero
		jz	:noData			; MSB = LSB = 0

:packetHasData	call	@TCPAppRxBytes	; inform app how many bytes available
		_bank	TCP_BANK
		inc	tcpLengthMSB
:processData	call	@NICReadAgain	; receive a byte
		call	@TCPAppRxData	; pass the byte to the application
		_bank	TCP_BANK	; _bank cus we called TCPAppRxData
					; priorly
		decsz	tcpLengthLSB
		jmp	:processData
		decsz	tcpLengthMSB
		jmp	:processData
		inc	tcpLengthLSB	; indicate for later there was Data
					; received

:noData		call	@TCPAckUpdate
		; send an ACK packet
		bank	TCP_BANK
		test	tcpLengthLSB	; was Data received?
		jz	:checkFIN	; no, it was an ACK packet. Just return
		call	@TCPAppRxDone	; indicate the packet was OK to the app

		_bank	TCP_BANK	; _bank cus we called TCPAppRxDone
					; priorly
		jb	tcpRxFlags.TCP_FLAG_FIN, :doClose	; if FIN bit set,
								; close the conn.
		call	@NICDumpRxFrame
		jmp	@TCPSendAck

:checkFIN	bank	TCP_BANK
		jb	tcpRxFlags.TCP_FLAG_FIN, :doClose	; is the FIN bit set?
		jmp	@NICDumpRxFrame

:doClose	call	@NICDumpRxFrame
		call	@TCPIncRcvNxt		; ACK the FIN
		mov	w, #TCP_ST_LASTACK	; change state
		bank	TCP_BANK
		sb	flags2.TCP_SOCK
		mov	tcp1State, w		; indicate tcp conn state to tcp conn
		snb	flags2.TCP_SOCK		; state variable
		mov	tcp2State, w
		jmp	@TCPSendFin

:gotoClosed	mov	w, #TCP_ST_CLOSED	; go to the closed state
		bank	TCP_BANK		; indicate tcp closed conn state to
		sb	flags2.TCP_SOCK		; correct tcp conn state variable
		mov	tcp1State, w
		snb	flags2.TCP_SOCK
		mov	tcp2State, w
		jmp	@NICDumpRxFrame		; discard received packet

:FINWAIT1	call	@NICDumpRxFrame
		bank	TCP_BANK
		sb	tcpRxFlags.TCP_FLAG_ACK	; check for ACK of FIN
		retp
		mov	w, #TCP_ST_FINWAIT2	; rcved ACK of FIN
		sb	flags2.TCP_SOCK		; indicate tcp finwait2 conn state to
		mov	tcp1State, w		; correct tcp conn state variable
		snb	flags2.TCP_SOCK
		mov	tcp2State, w
		retp

:FINWAIT2	call	@NICDumpRxFrame
		bank	TCP_BANK
		sb	tcpRxFlags.TCP_FLAG_FIN	; check for FIN
		retp
		mov	w, #TCP_ST_CLOSED	; rcved FIN
		sb	flags2.TCP_SOCK
		mov	tcp1State, w
		snb	flags2.TCP_SOCK
		mov	tcp2State, w
		call	@TCPIncRcvNxt		; ACK the FIN
		jmp	@TCPSendAck

:LASTACK	; ignore the packet
		; should check the packet is actually an ACK
		mov	w, #TCP_ST_CLOSED	; go to the closed state

		jb	flags2.TCP_SOCK, :lastackTcp2	; checkk which tcp conn
							; is current

		mov	tcp1State, w
		call	@DeleteSocket1		; delete the tcp conn. socket
		jmp	:dumpy

:lastackTcp2	mov	tcp2State, w
		call	@DeleteSocket2		; delete the tcp conn. socket

:dumpy		_bank	NIC_BANK		; needed for NICDumpRxFrame, we came from
						; an upper bank
		jmp	@NICDumpRxFrame

:CLOSED		call	@NICDumpRxFrame
		jmp	@TCPSendReset		; we shouldn't receive packets
						; while closed

:LISTEN		call	@NICDumpRxFrame		; discard received packet
		jb	flags2.TCP_SOCK, :listen2Check

		bank	TCB1_BANK
		snb	tcb1Flags.TCP_FLAG_RST	; check for an RST
		retp				; ignore a packet with RST
		snb	tcb1Flags.TCP_FLAG_ACK	; check for an ACK
		jmp	@TCPSendReset		; bad ACK, send a RST
		jmp	:contListen

:listen2Check	bank	TCB2_BANK
		snb	tcb2Flags.TCP_FLAG_RST	; check for an RST
		retp				; ignore a packet with RST
		snb	tcb2Flags.TCP_FLAG_ACK	; check for an ACK
		jmp	@TCPSendReset		; bad ACK, send a RST

:contListen	call	@TCPCopySeqToNxt
		call	@TCPSendSynAck
		bank	TCP_BANK
		mov	w, #TCP_ST_SYNRCVED	; change state
		sb	flags2.TCP_SOCK
		mov	tcp1State, w
		snb	flags2.TCP_SOCK
		mov	tcp2State, w
		retp

:SYNSENT	call	@NICDumpRxFrame
		jb	flags2.TCP_SOCK, :synsentCheck2

		bank	TCB1_BANK
		jnb	tcb1Flags.TCP_FLAG_ACK,	:noAck	; is the ACK bit set?
		jb	tcb1Flags.TCP_FLAG_RST,	:rst	; is the reset bit set?
		jnb	tcb1Flags.TCP_FLAG_SYN,	:noAck	; if SYN bit not set,
							; ignore packet
		jmp	:contSynsent

:synsentCheck2	bank	TCB2_BANK
		jnb	tcb2Flags.TCP_FLAG_ACK,	:noAck	; is the ACK bit set?
		jb	tcb2Flags.TCP_FLAG_RST,	:rst	; is the reset bit set?
		jnb	tcb2Flags.TCP_FLAG_SYN,	:noAck	; if SYN bit not set,
							; ignore packet

:contSynsent	bank	TCP_BANK
		mov	w, #TCP_ST_ESTABED	; the connection is now estabished
		sb	flags2.TCP_SOCK
		mov	tcp1State, w
		snb	flags2.TCP_SOCK
		mov	tcp2State, w
		bank	IP_BANK
		clr	ipLengthMSB		; set the received data length to 1
		mov	ipLengthLSB, #1		;  "
		call	@TCPAckUpdate
		jmp	@TCPSendAck

:rst		bank	TCP_BANK
		mov	w, #TCP_ST_CLOSED	; close the TCP
		sb	flags2.TCP_SOCK
		mov	tcp1State, w
		snb	flags2.TCP_SOCK
		mov	tcp2State, w
		retp

		; the peer wants us to raise the precedence. We can't.
		; We are not happy about not being Acked. Send a Reset.
:noAck		jmp	@TCPSendReset


	ORG	$800	; Page4

TCPRxHeader	jmp	_TCPRxHeader
TCPStartPktOut	jmp	_TCPStartPktOut
TCPTxByte	jmp	_TCPTxByte
TCPReTransmit	jmp	_TCPReTransmit
Compare4Inc	jmp	_Compare4Inc
TCPAppTxDone	jmp	_TCPAppTxDone

; ******************************************************************************
NICReadAgain_4
; Shortform for calling NICReadAgain(), which is in Page1 (This is in Page4)
; ******************************************************************************
		jmp	@NICReadAgain

; ******************************************************************************
NICWriteAgain_4
; Shortform for calling NICWriteAgain(), which is in Page1 (This is in Page4)
; ******************************************************************************
		jmp	@NICWriteAgain

; ******************************************************************************
NICDumpRxFrame_4
; Shortform for calling NICDumpRxFrame(), which is in Page1 (This is in Page4)
; ******************************************************************************
		jmp	@NICDumpRxFrame

; ******************************************************************************
TCPAddRcvNxt
; Add an 16-bit number to RCV.NXT
; INPUT:  {ipLengthMSB,ipLengthLSB} = number to add
; OUTPUT: {tcb1RcvNxt1-4,tcb2RcvNxt1-4}
; ******************************************************************************
		; check for which tcp connection to add
		mov	w, #tcb1RcvNxt1	; make pointer to TCB1_BANK
		sb	flags2.TCP_SOCK
		mov	globTemp1, w
		mov	w, #tcb2RcvNxt1	; make pointer to TCB2_BANK
		snb	flags2.TCP_SOCK
		mov	globTemp1, w

		bank	IP_BANK
		mov	w, ipLengthLSB
		mov	globTemp2, w		; store ipLengthLSB in globTemp2

		mov	fsr, globTemp1		; point to TCB variable
		add	indf, globTemp2		; add ipLengthLSB to tcbRcvNxt1

		bank	IP_BANK
		mov	w, ipLengthMSB
		snc
		mov	w, ++ipLengthMSB
		mov	globTemp2, w		; store in globTemp2

		dec	globTemp1
		mov	fsr, globTemp1		; point to TCB variable
		add	indf, globTemp2		; add to tcb1(2)RcvNxt2
		sc
		retp
		dec	fsr
		incsz	indf			; tcb1(2)RcvNxt3
		retp
		dec	fsr
		inc	indf			; tcb1(2)RcvNxt4
		retp

; ******************************************************************************
TCPIncRcvNxt
; Increment RCV.NXT by one
; INPUT:  none
; OUTPUT: {tcb1RcvNxt1-4,tcb2RcvNxt1-4}
; ******************************************************************************
		; set pointer to correct TCB (TCB1_BANK for tcp1,
		; TCB2_BANK for tcp2)
		mov	globTemp1, #(tcb1RcvNxt1-TCB1_BANK)
		call	@SetTCBPointer

		incsz	indf	; 1
		retp

		dec	fsr	; 2
		incsz	indf
		retp

		dec	fsr	; 3
		incsz	indf
		retp

		dec	fsr	; 4
		inc	indf
		retp

; ******************************************************************************
TCPIncSndUna
; Increment SND.UNA by one
; INPUT:  none
; OUTPUT: {tcb1SndUna1-4,tcb2SndUna1-4}
; ******************************************************************************
		; set pointer to correct TCB (TCB1_BANK for tcp1,
		; TCB2_BANK for tcp2)
		mov	globTemp1, #(tcb1SndUna1-TCB1_BANK)
		call	@SetTCBPointer

		incsz	indf	; 1
		retp

		dec	fsr	; 2
		incsz	indf
		retp

		dec	fsr	; 3
		incsz	indf
		retp

		dec	fsr	; 4
		inc	indf
		retp

; ******************************************************************************
TCPCopySeqToNxt
; Copy {tcpTmpSeq4-1} -> {tcb1RcvNxt4-1} or {tcb2RcvNxt4-1}
; INPUT:  {tcpTmpSeq4-1}
; OUTPUT: {tcb1RcvNxt4-1,tcb2RcvNxt4-1}
; ******************************************************************************
		mov	w, #tcb1RcvNxt4	; make pointer to TCB1_BANK
		sb	flags2.TCP_SOCK
		mov	globTemp1, w
		mov	w, #tcb2RcvNxt4	; make pointer to TCB2_BANK
		snb	flags2.TCP_SOCK
		mov	globTemp1, w
		mov	globTemp3, #tcpTmpSeq4	; make pointer to TCP BANK
		call	@Copy4Inc		; copy 4 TCP variables to TCB
		retp				; by incrementing the pointers

; ******************************************************************************
TCPCopyAckToUna
; Copy {tcpTmpAck4-1} -> {tcb1SndUna4-1} or {tcb2SndUna4-1}
; INPUT:  {tcpTmpAck4-1}
; OUTPUT: {tcb1SndUna4-1,{tcb2SndUna4-1}}
; ******************************************************************************
		mov	w, #tcb1SndUna4	; make pointer to TCB1_BANK
		sb	flags2.TCP_SOCK
		mov	globTemp1, w
		mov	w, #tcb2SndUna4	; make pointer to TCB2_BANK
		snb	flags2.TCP_SOCK
		mov	globTemp1, w
		mov	globTemp3, #tcpTmpAck4	; make pointer to TCP BANK
		call	@Copy4Inc		; copy 4 TCP variables to TCB
		retp

; ******************************************************************************
TCPAckUpdate
; Update SND.UNA and RCV.NXT
; INPUT:  {tcpTmpAck4-1}
;	  {tcpTmpSeq4-1}
;	  {ipLengthMSB,ipLengthLSB} = length of received TCP Data
; OUTPUT: {tcpSndUna4-1}
;	  {tcpRcvNxt4-1}
; ******************************************************************************
		call	@TCPCopyAckToUna	; set SND.UNA = SEG.ACK
		call	@TCPCopySeqToNxt	; set RCV.NXT = SEG.SEQ
		jmp	@TCPAddRcvNxt		; add the length of the received
						; packet to the ACK

; ******************************************************************************
TCPCmpNxtSeq
; Check if RCV.NXT == SEG.SEQ
; INPUT:  {tcpTmpSeq4-1} = SEG.SEQ
;	  {tcb1RcvNxt4-1} = RCV.NXT or {tcb2RcvNxt4-1} = RCV.NXT
; OUTPUT: z is set if RCV.NXT == SEG.SEQ
; ******************************************************************************
		mov	w, #tcb1RcvNxt1	; make pointer to TCB1_BANK
		sb	flags2.TCP_SOCK
		mov	globTemp1, w
		mov	w, #tcb2RcvNxt1	; make pointer to TCB2_BANK
		snb	flags2.TCP_SOCK
		mov	globTemp1, w
		mov	globTemp3, #tcpTmpSeq1	; make pointer to TCP BANK

		bank	IP_BANK
		mov	counter1, #4		; load the counter
:loop
		mov	fsr, globTemp3		; move tcp pointer to fsr
		mov	w, indf			; move tcp var in w
		mov	globTemp2, w		; store tcp var in global2

		mov	fsr, globTemp1		; move tcb pointer to fsr
		mov	w, indf			; move tcb var into w

		xor	w, globTemp2		; xor tcp var with tcb var
		jnz	:CmpEnd			; test if equal

		dec	globTemp1		; dec pointer to tcb
		dec	globTemp3		; dec pointer to tcp

		_bank	IP_BANK			; check loop counter until
						; finished
		dec	counter1
		sz
		jmp	:loop

:CmpEnd		retp

; ******************************************************************************
TCPSendEmptyPkt
; Constructs and sends a TCP packet containing no Data
; INPUT:  {remoteIP0-3} = Destination IP addr for TCP pkt
;	  {tcb1SndUna4-1} or {tcb2SndUna4-1} = sequence number
;	  {tcb1RcvNxt4-1} or {tcb2RcvNxt4-1} = acknowledgement number
;	  tcb1Flags or tcb2Flags = code flags
;	  {tcb1LocalPortMSB,tcb1LocalPortLSB} = TCP Source Port
;	  or {tcb2LocalPortMSB,tcb2LocalPortLSB} = TCP Source Port
;	  {tcb1RemotePortMSB,tcb1RemotePortLSB} = TCP Destination Port
;	  or {tcb2RemotePortMSB,tcb2RemotePortLSB} = TCP Destination Port
; OUTPUT:
; ******************************************************************************
		call	@TCPCheckSumInit
		bank	TCP_BANK
		clr	tcpLengthMSB
		clr	tcpLengthLSB
		call	TCPStartPktOut
		call	@NICSendTxFrame
		bank	TIMER_BANK

		; clear correct tcp connection's re-tx counters
		jb	flags2.TCP_SOCK, :clrTcp2timer

		; tcp conn1
		clr	tcp1TimerMSB
		clr	tcp1TimerLSB
		retp

		; tcp conn2
:clrTcp2timer	clr	tcp2TimerMSB
		clr	tcp2TimerLSB
		retp

; ******************************************************************************
TCPSendReset
; Send a reset packet with <SEQ=SEG.ACK><CTL=RST> and Discard the received
; packet
; INPUT:  {remoteIP0-3} = Destination IP addr for TCP pkt
;	  {tcb1RcvNxt4-1} or {tcb2RcvNxt4-1} = acknowledgement number
;	  {tcb1LocalPortMSB,tcb1LocalPortLSB} = TCP Source Port
;	  or {tcb2LocalPortMSB,tcb2LocalPortLSB} = TCP Source Port
;	  {tcb1RemotePortMSB,tcb1RemotePortLSB} = TCP Destination Port
;	  or {tcb2RemotePortMSB,tcb2RemotePortLSB} = TCP Destination Port
; OUTPUT:
; ******************************************************************************
		; point to correct tcb for current tcp connection
		mov	globTemp1, #(tcb1Flags-TCB1_BANK)
		call	@SetTCBPointer
		mov	indf, #(1<<TCP_FLAG_RST)

		call	@TCPCopyAckToUna	; copy the acknowledgement number.
		call	@TCPSendEmptyPkt
		jmp	NICDumpRxFrame_4	; discard the received pkt

; ******************************************************************************
TCPSendSyn
; Send a SYN packet
; INPUT:  {remoteIP0-3} = Destination IP addr for TCP pkt
;	  {tcb1RcvNxt4-1} or {tcb2RcvNxt4-1} = acknowledgement number
;	  {tcb1LocalPortMSB,tcb1LocalPortLSB} = TCP Source Port
;	  or {tcb2LocalPortMSB,tcb2LocalPortLSB} = TCP Source Port
;	  {tcb1RemotePortMSB,tcb1RemotePortLSB} = TCP Destination Port
;	  or {tcb2RemotePortMSB,tcb2RemotePortLSB} = TCP Destination Port
; OUTPUT: {tcpSndUna4-1} = new sequence number
; ******************************************************************************
		; point to correct tcb for current tcp connection
		mov	globTemp1, #(tcb1Flags-TCB1_BANK)
		call	@SetTCBPointer
		mov	indf, #(1<<TCP_FLAG_SYN)
		jmp	TCPSendISN

; ******************************************************************************
TCPSendISN
; Send the TCP initial sequence number
; INPUT:  {remoteIP0-3} = Destination IP addr for TCP pkt
;	  {tcb1RcvNxt4-1} or {tcb2RcvNxt4-1} = acknowledgement number
;	  {tcb1LocalPortMSB,tcb1LocalPortLSB} = TCP Source Port
;	  or {tcb2LocalPortMSB,tcb2LocalPortLSB} = TCP Source Port
;	  {tcb1RemotePortMSB,tcb1RemotePortLSB} = TCP Destination Port
;	  or {tcb2RemotePortMSB,tcb2RemotePortLSB} = TCP Destination Port
; OUTPUT: {tcpSndUna4-1} = new sequence number
; ******************************************************************************
		; obtain a random number for starting sequence number
		bank	NIC_BANK
		mov	w, nicCurrPktPtr
		xor	w, nicRemoteEth0
		bank	IP_BANK
		xor	w, ipIdentLSB
		mov	globTemp2, w	; store

		; point to the correct tcb for current tcp connection
		mov	globTemp1, #(tcb1SndUna4-TCB1_BANK)
		call	@SetTCBPointer

		mov	w, globTemp2	; restore

		mov	indf, w		; 1
		inc	fsr
		mov	indf, w		; 2
		inc	fsr
		mov	indf, w		; 3
		inc	fsr
		mov	indf, w		; 4

		call	@TCPIncRcvNxt
		call	@TCPSendEmptyPkt
		jmp	@TCPIncSndUna

; ******************************************************************************
TCPSendSynAck
; Send an SYN-ACK packet with <SEQ=SND.NXT><ACK=RCV.NXT><CTL=SYN>
; INPUT:  {remoteIP0-3} = Destination IP addr for TCP pkt
;	  {tcb1RcvNxt4-1} or {tcb2RcvNxt4-1} = acknowledgement number
;	  {tcb1LocalPortMSB,tcb1LocalPortLSB} = TCP Source Port
;	  or {tcb2LocalPortMSB,tcb2LocalPortLSB} = TCP Source Port
;	  {tcb1RemotePortMSB,tcb1RemotePortLSB} = TCP Destination Port
;	  or {tcb2RemotePortMSB,tcb2RemotePortLSB} = TCP Destination Port
; OUTPUT: {tcpSndUna4-1} = new sequence number
; ******************************************************************************
		; point to correct tcb for current tcp connection
		mov	globTemp1, #(tcb1Flags-TCB1_BANK)
		call	@SetTCBPointer
		mov	indf, #((1<<TCP_FLAG_SYN)|(1<<TCP_FLAG_ACK))

		jmp	@TCPSendISN

; ******************************************************************************
TCPSendAck
; Send an ACK packet with <SEQ=SND.NXT><ACK=RCV.NXT><CTL=ACK> and Discard the
; received packet
; INPUT:  {remoteIP0-3} = Destination IP addr for TCP pkt
;	  {tcb1SndUna4-1} or {tcb2SndUna4-1} = sequence number
;	  {tcb1RcvNxt4-1} or {tcb2RcvNxt4-1} = acknowledgement number
;	  {tcb1LocalPortMSB,tcb1LocalPortLSB} = TCP Source Port
;	  or {tcb2LocalPortMSB,tcb2LocalPortLSB} = TCP Source Port
;	  {tcb1RemotePortMSB,tcb1RemotePortLSB} = TCP Destination Port
;	  or {tcb2RemotePortMSB,tcb2RemotePortLSB} = TCP Destination Port
; OUTPUT:
; ******************************************************************************
		; point to correct tcb for current tcp connection
		mov	globTemp1, #(tcb1Flags-TCB1_BANK)
		call	@SetTCBPointer
		mov	indf, #(1<<TCP_FLAG_ACK)

		call	@TCPSendEmptyPkt
		jmp	NICDumpRxFrame_4	; discard the received pkt

; ******************************************************************************
TCPSendFin
; Send a FIN packet and discard the received packet
; INPUT:  {remoteIP0-3} = Destination IP addr for TCP pkt
;	  {tcb1SndUna4-1} or {tcb2SndUna4-1} = sequence number
;	  {tcb1RcvNxt4-1} or {tcb2RcvNxt4-1} = acknowledgement number
;	  tcb1Flags or tcb2Flags = code flags
;	  {tcb1LocalPortMSB,tcb1LocalPortLSB} = TCP Source Port
;	  or {tcb2LocalPortMSB,tcb2LocalPortLSB} = TCP Source Port
;	  {tcb1RemotePortMSB,tcb1RemotePortLSB} = TCP Destination Port
;	  or {tcb2RemotePortMSB,tcb2RemotePortLSB} = TCP Destination Port
; OUTPUT:
; ******************************************************************************
		; point to correct tcb for current tcp connection
		mov	globTemp1, #(tcb1Flags-TCB1_BANK)
		call	@SetTCBPointer

		mov	indf, #(1<<TCP_FLAG_FIN)|(1<<TCP_FLAG_ACK)
		call	@TCPSendEmptyPkt
		jmp	NICDumpRxFrame_4	; discard the received pkt

; ******************************************************************************
TCPCheckSumInit
; Clear TCP checksum value to prepare for new checksum calculation
; INPUT:  none
; OUTPUT: {tcpCheckSumMSB,tcpCheckSumLSB}
; ******************************************************************************
		bank	TCP_BANK
		clr	tcpCheckSumMSB
		clr	tcpCheckSumLSB
		clrb	flags.TCP_CHKSUM_LSB	; next byte is MSB
		retp

; ******************************************************************************
TCPCheckSumAcc
; Accumulate the TCP checksum. Checksum is computed by Doing the one's
; complement of the one's complement sum of 16-bit numbers
; INPUT:  w = byte to accumulate
;	  flags.TCP_CHKSUM_LSB = set if processing LSB, clear if processing MSB
; OUTPUT: {tcpCheckSumMSB,tcpCheckSumLSB}
; ******************************************************************************
		bank	TCP_BANK

		jnb	flags.TCP_CHKSUM_LSB, :msb	; are we processing an MSB?

:lsb		add	tcpCheckSumLSB,	w		; add it to the checksum
		sc					; was there a carry?
		jmp	:done
		inc	tcpCheckSumMSB			; yes
		snz
		inc	tcpCheckSumLSB
		jmp	:done

:msb		add	tcpCheckSumMSB,	w		; add it to the checksum
		sc					; was there a carry?
		jmp	:done
		inc	tcpCheckSumLSB			; yes, this time it is
							; added to the LSB
		snz
		inc	tcpCheckSumMSB

:done		xor	flags, #(1<<TCP_CHKSUM_LSB)
		retp

; ******************************************************************************
TCPCheckSumAddHdr
; Add to the TCP checksum, the pseudo-header fields
; INPUT:  {myIP0-3} = source IP addr
;	  {remoteIP0-3} = Destination IP addr
;	  {tcpLengthMSB,tcpLengthLSB} = length of TCP header and Data
; OUTPUT: {tcpCheckSumMSB,tcpCheckSumLSB}
; ******************************************************************************
		bank	TCP_BANK

		; <TCP_length>
		mov	w, tcpLengthMSB
		call	TCPCheckSumAcc
		mov	w, tcpLengthLSB
		call	TCPCheckSumAcc

		; <zero>,<protocol>
		mov	w, #0
		call	TCPCheckSumAcc
		mov	w, #6
		call	TCPCheckSumAcc

		; <source_IP>
		bank	IP_BANK
		mov	w, myIP3
		call	TCPCheckSumAcc
		bank	IP_BANK
		mov	w, myIP2
		call	TCPCheckSumAcc
		bank	IP_BANK
		mov	w, myIP1
		call	TCPCheckSumAcc
		bank	IP_BANK
		mov	w, myIP0
		call	TCPCheckSumAcc

		; <destination_IP>
		bank	IP_BANK
		mov	w, remoteIP3
		call	TCPCheckSumAcc
		bank	IP_BANK
		mov	w, remoteIP2
		call	TCPCheckSumAcc
		bank	IP_BANK
		mov	w, remoteIP1
		call	TCPCheckSumAcc
		bank	IP_BANK
		mov	w, remoteIP0
		call	TCPCheckSumAcc

		retp

; ******************************************************************************
_TCPTxByte
; Transmit a TCP byte accumulating the checksum each time
; INPUT:  w = byte to send
; OUTPUT: none
; ******************************************************************************
		mov	globTemp1, w
		call	@TCPCheckSumAcc
		mov	w, globTemp1
		call	NICWriteAgain_4
		retp

; ******************************************************************************
_TCPStartPktOut
; Constructs the TCP and IP headers
; INPUT:  {remoteIP0-3} = Destination IP addr for TCP pkt
;	  {tcpLengthMSB,tcpLengthLSB} = length of TCP Data (just Data)
;	  {tcpCheckSumMSB,tcpCheckSumLSB} = TCP checksum computed over just Data
;	  {tcb1SndUna4-1} or {tcb2SndUna4-1} = sequence number
;	  {tcb1RcvNxt4-1} or {tcb2RcvNxt4-1} = acknowledgement number
;	  tcb1Flags or tcb2Flags = code flags
;	  {tcb1LocalPortMSB,tcb1LocalPortLSB} = TCP Source Port
;	  or {tcb2LocalPortMSB,tcb2LocalPortLSB} = TCP Source Port
;	  {tcb1RemotePortMSB,tcb1RemotePortLSB} = TCP Destination Port
;	  or {tcb2RemotePortMSB,tcb2RemotePortLSB} = TCP Destination Port
; OUTPUT:
; ******************************************************************************
		; tcpLength += <TCP header length>
		_bank	TCP_BANK
		mov	w, #(TCP_HDR_LENGTH<<2)	; add in size of TCP hdr (20)
		add	tcpLengthLSB, w
		snc
		inc	tcpLengthMSB		; tcpLength now is the length of
						; TCP hdr and Data

		; IP <total_length> = tcpLength + <IP header length>
		mov	w, #20			; add in size of IP hdr (20)
		add	w, tcpLengthLSB
		bank	IP_BANK
		mov	ipLengthLSB, w
		bank	TCP_BANK
		mov	w, tcpLengthMSB
		bank	IP_BANK
		mov	ipLengthMSB, w
		snc
		inc	ipLengthMSB

		; update IP <identifier>
		inc	ipIdentLSB
		snz
		inc	ipIdentMSB

		; set IP <protocol> for TCP
		mov	ipProtocol, #6

		; We should rather load remoteIP with tcp socketIP here, but Due
		; to limited code space it is Done in IPGenCheckSum.

		; compute IP <header_checksum>
		call	@IPGenCheckSum

		; now we're ready to construct the IP header
		call	@IPStartPktOut

		; then construct the TCP header

		; point to correct tcb for current tcp connection
		mov	globTemp1, #(tcb1Offset-TCB1_BANK)
		call	@SetTCBPointer

		; TCP <source_port>,<destination_port>,<sequence_number>,
		; <acknowledgement_number>,<hlen>,<code>,<window>
		mov	indf, #(TCP_HDR_LENGTH<<4)
		inc	fsr
		inc	fsr
		mov	indf, #((TCP_WINDOW_SIZE&$FF00)>>8)
		inc	fsr
		mov	indf, #(TCP_WINDOW_SIZE&$00FF)

		; make a pointer to the tcb for the current tcp connection
		mov	w, #TCB1_BANK
		sb	flags2.TCP_SOCK
		mov	globTemp3, w
		mov	w, #TCB2_BANK
		snb	flags2.TCP_SOCK
		mov	globTemp3, w

:loop		mov	fsr, globTemp3
		mov	w, indf			; load the value
		call	@TCPTxByte		; transmit and accumulate header
						; checksum
		inc	globTemp3

		jb	flags2.TCP_SOCK, :check2
		cse	globTemp3, #TCB1_END	; is the loop finished?
		jmp	:loop
		jmp	:outloop

:check2		cse	globTemp3, #TCB2_END	; is the loop finished?
		jmp	:loop

:outloop	; TCP <checksum>
		call	@TCPCheckSumAddHdr
		bank	TCP_BANK
		mov	w, /tcpCheckSumMSB
		call	NICWriteAgain_4
		mov	w, /tcpCheckSumLSB
		call	NICWriteAgain_4

		; TCP <urgent_ptr>
		mov	w, #0
		call	NICWriteAgain_4
		call	NICWriteAgain_4

		retp

; ******************************************************************************
_TCPRxHeader
; Process the TCP header of a received TCP packet
; INPUT:  none
; OUTPUT: Z is set to 1 if the packet is invalid, 0 otherwise
;	  {tcb1RemotePortMSB,tcb1RemotePortLSB}
;	  or {tcb2RemotePortMSB,tcb2RemotePortLSB}
;	  {tcb1SendWinMSB,tcb1SendWinLSB} or {tcb2SendWinMSB,tcb2SendWinLSB}
;	  tcb1Offset or tcb2Offset
;	  tcb1Flags or tcb2Flags
;	  tcpRxFlags
;	  {tcpTmpSeq4-1} = <sequence_number>
;	  {tcpTmpAck4-1} = <acknowledgement_number>
;	  {tcpLengthMSB,tcpLengthLSB} = length of TCP Data
;	  {ipLengthMSB,ipLengthLSB} = length of TCP Data
; ******************************************************************************
		bank	TCPPORT_BANK

		; <remote_port>
		call	NICReadAgain_4
		mov	tcpRemotePortMSB, w		; store remote port MSB
		call	NICReadAgain_4
		mov	tcpRemotePortLSB, w		; store remote port LSB

		; <destination_port>
		call	NICReadAgain_4
		mov	tcpLocalPortMSB, w	; store dest port MSB
		call	NICReadAgain_4		;
		mov	tcpLocalPortLSB, w	; store dest port LSB

		call	@TCPConnectionManager	; start the tcp conn/socket
						; manager
		snz				; is the packet OK?
		retp				; no, return

		; <sequence_number>
		bank	TCP_BANK
		call	NICReadAgain_4
		mov	tcpTmpSeq4, w
		call	NICReadAgain_4
		mov	tcpTmpSeq3, w
		call	NICReadAgain_4
		mov	tcpTmpSeq2, w
		call	NICReadAgain_4
		mov	tcpTmpSeq1, w

		_bank	TCPTMP_BANK
		; <acknowledgement_number>
		call	NICReadAgain_4
		mov	tcpTmpAck4, w
		call	NICReadAgain_4
		mov	tcpTmpAck3, w
		call	NICReadAgain_4
		mov	tcpTmpAck2,w
		call	NICReadAgain_4
		mov	tcpTmpAck1, w

		call	NICReadAgain_4		; receive the Data offset.
				;	Used to skip the options
		and	w, #TCP_OFFSET_MASK	; mask out the offset
		mov	globTemp2, w		; store	w

		; point to the correct tcb for the current tcp connection
		mov	globTemp1, #(tcb1Offset-TCB1_BANK)
		call	@SetTCBPointer

		mov	indf, globTemp2
		clc
		rr	indf
		rr	indf

		; ipLength = tcpLength = length of TCP Data
		mov	w, indf

		_bank	IP_BANK
		sub	ipLengthLSB, w		; subtract out size of TCP header
		mov	w, ipLengthLSB
		bank	TCP_BANK
		mov	tcpLengthLSB, w
		bank	IP_BANK
		sc
		dec	ipLengthMSB
		mov	w, ipLengthMSB
		bank	TCP_BANK
		mov	tcpLengthMSB, w

		; <code>
		call	NICReadAgain_4		; receive the flags
		mov	tcpRxFlags, w
		mov	globTemp2, w
		; point to the correct tcb for the current tcp connection
		mov	globTemp1, #(tcb1Flags-TCB1_BANK)
		call	@SetTCBPointer
		mov	indf, globTemp2

		; <window>
		call	NICReadAgain_4
		mov	globTemp2, w

		; point to the correct tcb for the current tcp connection
		mov	globTemp1, #(tcb1SendWinMSB-TCB1_BANK)
		call	@SetTCBPointer
		mov	indf, globTemp2		; receive the window

		call	NICReadAgain_4
		mov	globTemp2, w

		; point to the correct tcb for the current tcp connection
		mov	globTemp1, #(tcb1SendWinLSB-TCB1_BANK)
		call	@SetTCBPointer
		mov	indf, globTemp2

		; point to the correct tcb for the current tcp connection
		mov	globTemp1, #(tcb1Offset-TCB1_BANK)
		call	@SetTCBPointer
		sub	indf, #((TCP_HDR_LENGTH<<2)-4)
		mov	w, indf
		mov	globTemp3, w
		clr	indf

:loop		call	NICReadAgain_4
		decsz	globTemp3
		jmp	:loop
		clz
		retp

; ******************************************************************************
_TCPReTransmit
; This is called to retransmit the TCP packet that's already setup in the NIC's
; TCP transmit buffer. Remember that a UDP/ICMP/ARP packet may have been sent
; after the TCP packet was initially sent, so we have to re-setup the NIC
; carefully.
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		; re-initialize the NIC's TPSR
		bank	NIC_BANK
		clr	nicIOAddr		; CR
:wait		call	@NICRead
		jb	wreg.2,	:wait		; wait for prior transmission to
						; complete
		mov	w, #%00100010		; Page0, abort DMA
		call	@NICWrite
		mov	nicIOAddr, #$04		; TPSR

		; point to correct tcp tx buffer for current tcp connection
		sb	flags2.TCP_SOCK
		mov	w, #TXBUF2_START
		snb	flags2.TCP_SOCK
		mov	w, #TXBUF3_START

		call	@NICWrite

		; read the NIC's TCP transmit buffer to find out the IP <length>
		; so that we can re-initialize the NIC's TBCR
		sb	flags2.TCP_SOCK
		mov	w, #TXBUF2_START
		snb	flags2.TCP_SOCK
		mov	w, #TXBUF3_START

		mov	nicCopySrcMSB, w
		mov	nicCopySrcLSB, #(6+6+2+2)	; IP <length> (MSB)
		call	@NICBufRead
		bank	IP_BANK
		mov	ipLengthMSB, w
		bank	NIC_BANK
		inc	nicCopySrcLSB		; IP <length> (LSB)
		call	@NICBufRead
		bank	IP_BANK
		mov	ipLengthLSB, w

		jmp	@NICSendTxFrame		; re-transmit the ethernet frame

; ******************************************************************************
_Compare4Inc
; Compares 4 variables in Databanks
; INPUT: globTemp1 = pointer1, globTemp3 = pointer2
; OUTPUT: Z is set on a full match
; ******************************************************************************
		bank	IP_BANK
		mov	counter1, #4		; load the counter
:loop
		mov	fsr, globTemp3		; move pointer to fsr
		mov	w, indf			; move var in w
		mov	globTemp2, w		; store var in global2

		mov	fsr, globTemp1		; move pointer to fsr
		mov	w, indf			; move var into w

		xor	w, globTemp2		; xor tcp var with tcb var
		jnz	:CmpEnd			; test if equal

		inc	globTemp1		; increment pointer
		inc	globTemp3		; increment pointer

		_bank	IP_BANK			; check loop counter until finished
		dec	counter1
		sz
		jmp	:loop

:CmpEnd		retp

; ******************************************************************************
_TCPAppTxDone
; This is called following the last call to TCPAppTxData(). It signifies the
; transmitted Data has successfully reached the remote host
; [TCP API Function]
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		; jump to current tcp connection code
		jb	flags2.TCP_SOCK, :TCPAppTxDone2

		; tcp1
		retp

		; tcp2
:TCPAppTxDone2	retp


	ORG	$A00	; Page5

TCPAppRxData	jmp	_TCPAppRxData

; ******************************************************************************
TCPAppRxBytes
; Indicator to the application that a packet has been received and that
; TCPAppRxByte is about to be called as many times as they are bytes of data
; [TCP API Function]
; INPUT:  {tcpAppRxBytesMSB,tcpAppRxBytesLSB} = number of received Data bytes
; OUTPUT: none
; ******************************************************************************
		; jump to current tcp connection code
		jb	flags2.TCP_SOCK, :TCPAppRxBytes2

		; tcp1
	IF	SMTP
		mov	globTemp2, #3			; set rcv byte counter
		mov	globTemp3, #smtpReplyCode3	; set pointer to rcv buffer
	ENDIF
		retp

		; tcp2
:TCPAppRxBytes2	retp

; ******************************************************************************
TCPAppTxBytes
; Called before transmitting a TCP packet to see if the application has any
; Data it wishes to send. The application cannot send more than TCP_SEG_SIZE
; bytes at one go.
; [TCP API Function]
; INPUT:  none
; OUTPUT: {tcp1UnAckMSB,tcp1UnAckLSB}
;	  or {tcp2UnAckMSB,tcp2UnAckLSB} = number of bytes to transmit
; ******************************************************************************
		; check if tcp1 or tcp2's chance to transmit
		jb	flags2.TCP_TXSEMA, :tcpConn2Tx

		; tcp1
		clrb	flags2.TCP_SOCK			; indicate tcp1 conn.
		cse	tcp1State, #TCP_ST_ESTABED	; check if in
							; established state
		retp					; no, return

	IF	SMTP
		sb	flags2.SMTP_TXEN		; check if we may tx
		retp					; no
		clrb	flags2.SMTP_TXEN		; yes, clear for next tx

		_bank	SMTP_BANK
		cje	smtpState, #SMTP_HELO,	:smtpSndHelo	; chk if have to
								; tx HELO
		cje	smtpState, #SMTP_MAIL,	:smtpSndMail	; chk if have to
								; tx MAIL FROM
		cje	smtpState, #SMTP_RCPT,	:smtpSndRcpt	; chk if have to
								; tx RCPT TO
		cje	smtpState, #SMTP_DATA,	:smtpSndData	; chk if have to
								; tx DATA
		cje	smtpState, #SMTP_TEXT,	:smtpSndText	; chk if have to
								; tx msg text
		cje	smtpState, #SMTP_QUIT,	:smtpSndQuit	; chk if have to
								; tx QUIT
		cje	smtpState, #SMTP_CLOSE,	:smtpClose	; chk if have to
								; close
		;retp	 ; nothing?

:smtpSndHelo	mov	smtpStrPointer,	#(SMTPTEXT_HELO)&255	; make a pointer to the
								; char string.
		bank	TCP_BANK
		mov	tcp1UnAckLSB, #(SMTPTEXT_HELO_END-SMTPTEXT_HELO)
		retp

:smtpSndMail	mov	smtpStrPointer,	#(SMTPTEXT_MAIL)&255	; make a pointer to the
								; char string.
		bank	TCP_BANK
		mov	tcp1UnAckLSB, #(SMTPTEXT_MAIL_END-SMTPTEXT_MAIL)
		retp

:smtpSndRcpt	mov	smtpStrPointer,	#(SMTPTEXT_RCPT)&255	; make a pointer to the
								; char string.
		bank	TCP_BANK
		mov	tcp1UnAckLSB, #(SMTPTEXT_RCPT_END-SMTPTEXT_RCPT)
		retp

:smtpSndData	mov	smtpStrPointer,	#(SMTPTEXT_DATA)&255	; make a pointer to the
								; char string.
		bank	TCP_BANK
		mov	tcp1UnAckLSB, #(4+2)			; DATA plus <CRLF>
		retp

:smtpSndText	mov	smtpStrPointer,	#(SMTPTEXT_TEXT)&255	; make a pointer to the
								; char string.
		bank	TCP_BANK
		mov	tcp1UnAckLSB, #(SMTPTEXT_TEXT_END-SMTPTEXT_TEXT)
		retp

:smtpSndQuit	mov	smtpStrPointer,	#(SMTPTEXT_QUIT)&255	; make a pointer to the
								; char string.
		bank	TCP_BANK
		mov	tcp1UnAckLSB, #(4+2)			; QUIT plus <CRLF>
		retp

:smtpClose	; the remote host will initiate the close
		retp
	ENDIF
		retp

		; tcp2
:tcpConn2Tx	setb	flags2.TCP_SOCK			; indicate tcp2 conn.
		cse	tcp2State, #TCP_ST_ESTABED	; check if in
							; established state
		retp					; no, return

	IF	HTTP
		snb	flags2.BROWSER_TYPE	; do not send anything to netscape
						; type browser on a post.
		retp
				
		bank	HTTP_BANK
		cje	httpParseState,	#2, :state2
		cje	httpParseState,	#3, :state3
		cje	httpParseState,	#4, :state4
		retp

:state2		; check how much there is to send
		_bank	EEPROM_BANK
		csae	e2FileLenMSB, #(HTTP_SEG_SIZE>>8)
		jmp	:lastSegment				;	msb#1 < msb#2
		cse	e2FileLenMSB, #(HTTP_SEG_SIZE>>8)
		jmp	:notLast				;	msb#1 > msb#2
		csa	e2FileLenLSB, #(HTTP_SEG_SIZE&$00FF)
		jmp	:lastSegment				;	#1 <= #2

:notLast	; not the last segment so send as much as possible
		; (i.e. full segment)
		sub	e2FileLenLSB, #(HTTP_SEG_SIZE&$00FF)	;	e2FileLen
								;	-= HTTP_SEG_SIZE
		sc						;
		dec	e2FileLenMSB				;
		sub	e2FileLenMSB, #(HTTP_SEG_SIZE>>8)	;
		_bank	TCP_BANK
		mov	tcp2UnAckMSB, #(HTTP_SEG_SIZE>>8)
		mov	tcp2UnAckLSB, #(HTTP_SEG_SIZE&$00FF)
		retp

:lastSegment	; last segment so send whatever is leftover
		mov	w, e2FileLenMSB
		_bank	TCP_BANK
		mov	tcp2UnAckMSB, w
		_bank	EEPROM_BANK
		mov	w, e2FileLenLSB
		_bank	TCP_BANK
		mov	tcp2UnAckLSB, w
		bank	HTTP_BANK
		inc	httpParseState	; next-state = 3
		retp

		; no more to send so we close
:state3		call	@TCPAppClose
		retp

:state4
	ENDIF
		retp

; ******************************************************************************
TCPAppTxData
; This routine is called once for each byte the application has says it wishes
; to transmit.
; [TCP API Function]
; INPUT:  none
; OUTPUT: w = Data byte to transmit
; ******************************************************************************
		; jump to current tcp connection code
		jb	flags2.TCP_SOCK, :TCPAppTxData2

		; tcp1
	IF	SMTP
		_bank	SMTP_BANK
		cje	smtpState, #SMTP_HELO,	:smtpTxHelo	; chk if we have
								; to tx HELO
		cje	smtpState, #SMTP_MAIL,	:smtpTxMail	; chk if we have
								; to tx MAIL FROM
		cje	smtpState, #SMTP_RCPT,	:smtpTxRcpt	; chk if we have
								; to tx RCPT TO
		cje	smtpState, #SMTP_DATA,	:smtpTxData	; chk if we have
								; to tx DATA
		cje	smtpState, #SMTP_TEXT,	:smtpTxText	; chk if we have
								; to tx msg text
		cje	smtpState, #SMTP_QUIT,	:smtpTxQuit	; chk if we have
								; to tx QUIT
		retp	; nothing?

		; we have to send a HELO<CRLF>
:smtpTxHelo	mov	w, #(SMTPTEXT_HELO >> 8)	; upper nibble
		jmp	smtpRdPrgMemData

		; we have to send a MAIL FROM: <sender>
:smtpTxMail	mov	w, #(SMTPTEXT_MAIL >> 8)	; upper nibble
		jmp	smtpRdPrgMemData

		; we have to send a RCPT TO: <recipient>
:smtpTxRcpt	mov	w, #(SMTPTEXT_RCPT >> 8)	; upper nibble
		jmp	smtpRdPrgMemData

		; we have to send a DATA
:smtpTxData	mov	w, #(SMTPTEXT_DATA >> 8)	; upper nibble
		jmp	smtpRdPrgMemData

		; we have to send the email text
:smtpTxText	mov	w, #(SMTPTEXT_TEXT >> 8)	; upper nibble
		jmp	smtpRdPrgMemData

		; we have to send a QUIT
:smtpTxQuit	mov	w, #(SMTPTEXT_QUIT >> 8)	; upper nibble
		jmp	smtpRdPrgMemData
	ENDIF
		retp

		; tcp2
:TCPAppTxData2
	IF	HTTP
		bank	HTTP_BANK
		cje	httpURIHash, #URI1, :specialFile	; resource.htm
		cje	httpURIHash, #URI2, :specialFile	; temperature.htm
		cje	httpURIHash, #URI3, :specialFile2	; postctrl.htm
		call	@E2Read8Ack
		retp

:specialFile	call	@E2Read8Ack
		mov	globTemp1, w		; save temporarily
		csb	globTemp1, #$F0
		jmp	:match
		mov	w, globTemp1
		retp

		; look for magic number in file indicating Dynamic content
:specialFile2	setb	flags3.LED_LOCK		 ; lock access to LED
		call	@E2Read8Ack
		mov	globTemp1, w		; save temporarily
		csb	globTemp1, #$F0
		jmp	:match2
		mov	w, globTemp1
		retp

:match2		; look if led is on or off now
		jb	LED_PORT.LED, :ledOffChar

		; it is on, return last char of image for ledon.gif
		mov	w, #'n'
		retp

		; it is off, return last char of image for ledof.gif
:ledOffChar	mov	w, #'f'
		retp

:match		cjne	globTemp1, #$F0, :subMatch	; 0xF0 is the magic number

:firstMatch	bank	HTTP_BANK
		mov	globTemp2, httpURIHash
		mov	fsr, #bcd3

		cjne	globTemp2, #URI1, :here
		mov	w, pageCount
		inc	pageCount
		jmp	:here1
:here		_bank	ADC_BANK
		mov	w, adc
:here1		call	@Bin8ToBCD
		mov	w, bcd3+0
		call	@BCDToASCII
		retp

:subMatch	sub	globTemp1, #$F0
		_bank	MISC_BANK
		mov	fsr, #bcd3
		add	fsr, globTemp1
		mov	w, indf
		call	@BCDToASCII
	ENDIF
		retp

; ******************************************************************************
_TCPAppRxData
; Called once for each byte received in a packet. Will only be called when
; tcpState is established.
; [TCP API Function]
; INPUT:  w = received Data byte
; OUTPUT: none
; ******************************************************************************
		; jump to current tcp connection code
		jb	flags2.TCP_SOCK, :TCPAppRxData2

		; tcp1
	IF	SMTP
		mov	globTemp1, w	; store the rcvd byte
		test	globTemp2	; check if we got the reply code,
					; the rest is junk
		snz
		retp			; yes, we got it, just return on junk
					; bytes

		; receive the reply code
		mov	fsr, globTemp3		; set pointer
		mov	indf, globTemp1		; move rcvd byte to rcv buffer
		inc	globTemp3		; advance pointer
		dec	globTemp2		; receive until counter done
		retp				; return to rcv some more bytes
	ENDIF
		retp

		; tcp2
:TCPAppRxData2
	IF	HTTP
		mov	globTemp1, w
		_bank	HTTP_BANK

		; check if this is 2nd packet from Netscape type browser, post
		jb	flags2.BROWSER_TYPE, :fieldSrch
		
		; look if we got the method already
		sb	flags3.GOT_HTTP_METHOD
		jmp	:methodSrch1		; no, go to method search

		; yes, we have the method, jump to correct method parser
		jb	flags3.HTTP_METHOD, :postMethod
		jmp	:defaultMethod

:methodSrch1	cje	globTemp1, #'P', :isPost	; is it a P ?
		clrb	flags3.HTTP_METHOD		; it's GET
		setb	flags3.GOT_HTTP_METHOD		; indicate we have the method
		jmp	:defaultMethod

:isPost		setb	flags3.HTTP_METHOD		; yes, so it must be POST or PUT
		setb	flags3.GOT_HTTP_METHOD		; indicate we have the method
		setb	flags3.LED_LOCK			; lock access to the LED
		clr	httpParseState
		clr	httpParseState2
		retp

:postMethod	; have to get the requested resource now to set file pointer
		jb	flags3.GOT_URI,	:fieldSrch	; check if we have to go
		test	httpParseState			; directly to field srch
		jz	:stateN0
		cje	httpParseState,	#1, :stateN1

:stateN0	cse	globTemp1, #' '			; search for the space after the method
		retp					; keyword
		inc	httpParseState			; got it, next-state = 1
		retp

:stateN1	cje	globTemp1, #' ', :gotURI	; make URI until space after
							; requested resource
		add	httpURIHash, globTemp1
		retp

		; we have to search for the input field now. It's after 2 CRLFs
:fieldSrch	cje	httpParseState,	#1, :gotCtrl
		cje	httpParseState,	#2, :gotLf
		cje	httpParseState,	#3, :gotBlkLine
		cje	httpParseState,	#4, :gotField

		csne	globTemp1, #$0d		; check for first ctrl
		inc	httpParseState		; yes, got it
		retp				; no

:gotCtrl	cjne	globTemp1, #$0a, :fldSrchRst
		inc	httpParseState		; yes, got it
		retp				; no

:gotLf		cjne	globTemp1, #$0d, :fldSrchRst	; check for blank line
		inc	httpParseState			; yes, got to blank line
		retp					; no

:gotBlkLine	cjne	globTemp1, #$0a, :fldSrchRst	; check for last char of
							; blank line
		inc	httpParseState			; yes, we are at the field now
		retp

:gotField	cje	httpParseState2, #1, :gotl
		cje	httpParseState2, #2, :gote
		cje	httpParseState2, #3, :gotd
		cje	httpParseState2, #4, :gotequal

		csne	globTemp1, #'l'		; look for 'l'
		inc	httpParseState2		; yes, got it
		retp				; no

:gotl		csne	globTemp1, #'e'		; look for 'e'
		inc	httpParseState2		; yes, got it
		retp				; no

:gote		csne	globTemp1, #'d'		; look for 'd'
		inc	httpParseState2		; yes, got it
		retp				; no

:gotd		csne	globTemp1, #'='		; look for '='
		inc	httpParseState2		; yes, got it
		retp				; no

		; now the next byte is the control byte for the led control
:gotequal	cje	globTemp1, #'1', :ledOn
		cje	globTemp1, #'0', :ledOff
		cje	globTemp1, #'t', :ledToggle
		retp

:ledOn		clrb	LED_PORT.LED		; switch led on
		retp

:ledOff		setb	LED_PORT.LED		; switch led off
		retp

:ledToggle	mov	w, #(1<<LED)		; toggle led status
		xor	LED_PORT, w
		retp

:fldSrchRst	clr	httpParseState		; no, gotta start over.
		retp

:defaultMethod	test	httpParseState
		jz	:state0
		cje	httpParseState,	#1, :state1
		cjae	httpParseState,	#2, :state2

:state0		cse	globTemp1, #' '		; search for the space after the method
		retp
		inc	httpParseState		; got it, next-state = 1
		retp

:state1		cje	globTemp1, #' ', :gotURI; make URI until space after
						; requested resource
		add	httpURIHash, globTemp1
		retp

:gotURI		; got the requested resource, obtain pointer to file
		; check if coming from post method
		jnb	flags3.HTTP_METHOD, :gotURICont

		; bit is set, so we came from post method
		clr	httpParseState	; clear for field parser
		setb	flags3.GOT_URI	; indicate we got URI for post field parser

:gotURICont	mov	w, httpURIHash
		_bank	EEPROM_BANK
		mov	e2AddrLSB, w
		clr	e2AddrMSB
		clc			; e2Addr = httpURIHash * 2
		rl	e2AddrLSB	;
		rl	e2AddrMSB	;
		call	@E2Init		; reset EEPROM
		call	@E2SetAddr
		call	@E2SendRdCmd
		call	@E2Read8Ack
		mov	e2AddrMSB, w
		call	@E2Read8NoAckStop
		mov	e2AddrLSB, w

		; start reading from file
		call	@E2SetAddr
		call	@E2SendRdCmd

		; file length
		call	@E2Read8Ack
		mov	e2FileLenMSB, w
		call	@E2Read8Ack
		mov	e2FileLenLSB, w

		; file checksum (ignore)
		call	@E2Read8Ack
		call	@E2Read8Ack

		_bank	HTTP_BANK
		sb	flags3.HTTP_METHOD	; do not increment if post
		inc	httpParseState		; next-state = 2
		retp

:state2		 ; here we receive bytes from after the requested resource
	ENDIF
		retp

	IF	SMTP
; ******************************************************************************
smtpRdPrgMemData	; not relocatable. Must be in same page as caller
; Reads SMTP Data stored in program memory.
; INPUT: w contains upper nibble of prog memory location.
;	 smtpPointer contains lower byte of prog mem location.
; OUTPUT: Data in w register
; ******************************************************************************
		mov	m, w			; into MODE register
		mov	w, smtpStrPointer	; lower byte (2 nibbles)
		inc	smtpStrPointer		; increment pointer
		iread	 ; read data from prog memory into w register
		retp
	ENDIF


	ORG	$C00	; Page6

TCPTransmit	jmp	_TCPTransmit
TCPAppRxDone	jmp	_TCPAppRxDone
TCPApp1Init	jmp	_TCPApp1Init


	IF	SMTP
smtpCompare220		jmp	_smtpCompare220
smtpCompare221		jmp	_smtpCompare221
smtpCompare250		jmp	_smtpCompare250
smtpCompare354		jmp	_smtpCompare354
	ENDIF

; ******************************************************************************
SetTCBPointer
; Sets the fsr register to point to the correct TCB
; INPUT:  globTemp1=offset into TCB1_BANK or TCB2_BANK
; OUTPUT: fsr contains pointer to variable in TCB bank
; ******************************************************************************
		; check if pointer to be set for tcp1 or 2 connection.
		jb	flags2.TCP_SOCK, :tcb2pointer

		; tcp1
		add	globTemp1, #TCB1_BANK
		mov	fsr, globTemp1	; make pointer to TCB1_BANK for tcp1
		retp

		; tcp2
:tcb2pointer	add	globTemp1, #TCB2_BANK
		mov	fsr, globTemp1	; make pointer to TCB2_BANK for tcp2
		retp

; ******************************************************************************
CheckSocket1IP
; Checks if remoteIP is in tcp socket1
; INPUT: remoteIP3-0, sock1RemoteIP3-0
; OUTPUT: Z set if in there
; ******************************************************************************
		mov	globTemp1, #remoteIP3
		mov	globTemp3, #sock1RemoteIP3
		call	@Compare4Inc
		retp

; ******************************************************************************
CheckSocket2IP
; Checks if remoteIP is in tcp socket2
; INPUT: remoteIP3-0, sock2RemoteIP3-0
; OUTPUT: Z set if in there
; ******************************************************************************
		mov	globTemp1, #remoteIP3
		mov	globTemp3, #sock2RemoteIP3
		call	@Compare4Inc
		retp

; ******************************************************************************
CopyRemotePortTCB1
; Copies tcpRemotePortMSB,LSB into tcb1RemotePortMSB,LSB
; INPUT: tcpRemotePortMSB,LSB
; OUTPUT: tcb1RemotePortMSB,LSB
; ******************************************************************************
		bank	TCPPORT_BANK
		mov	w, tcpRemotePortMSB
		bank	TCB1_BANK
		mov	tcb1RemotePortMSB, w

		bank	TCPPORT_BANK
		mov	w, tcpRemotePortLSB
		bank	TCB1_BANK
		mov	tcb1RemotePortLSB, w

		retp

; ******************************************************************************
CopyRemotePortTCB2
; Copies tcpRemotePortMSB,LSB into tcb2RemotePortMSB,LSB
; INPUT: tcpRemotePortMSB,LSB
; OUTPUT: tcb2RemotePortMSB,LSB
; ******************************************************************************
		bank	TCPPORT_BANK
		mov	w, tcpRemotePortMSB
		bank	TCB2_BANK
		mov	tcb2RemotePortMSB, w

		bank	TCPPORT_BANK
		mov	w, tcpRemotePortLSB
		bank	TCB2_BANK
		mov	tcb2RemotePortLSB, w

		retp

; ******************************************************************************
CheckLocalPortTCB1
; Compares tcpLocalPortLSB, tcpLocalPortMSB against tcb1LocalPortLSB,
;	   tcb1LocalPortMSB
; INPUT: tcpLocalPortLSB, tcpLocalPortMSB, tcb1LocalPortLSB, tcb1LocalPortMSB
; OUTPUT: Z set if match
; ******************************************************************************
		bank	TCPPORT_BANK
		mov	w, tcpLocalPortMSB
		bank	TCB1_BANK
		xor	w, tcb1LocalPortMSB
		sz
		retp
		mov	w, tcb1LocalPortLSB
		bank	TCPPORT_BANK
		xor	w, tcpLocalPortLSB
		retp

; ******************************************************************************
CheckLocalPortTCB2
; Compares tcpLocalPortLSB, tcpLocalPortMSB against tcb2LocalPortLSB,
;	   tcb2LocalPortMSB
; INPUT: tcpLocalPortLSB, tcpLocalPortMSB, tcb2LocalPortLSB, tcb2LocalPortMSB
; OUTPUT: Z set if match
; ******************************************************************************
		bank	TCPPORT_BANK
		mov	w, tcpLocalPortMSB
		bank	TCB2_BANK
		xor	w, tcb2LocalPortMSB
		sz
		retp
		mov	w, tcb2LocalPortLSB
		bank	TCPPORT_BANK
		xor	w, tcpLocalPortLSB
		retp

; ******************************************************************************
TCPConnectionManager
; Manages TCP connections. Checks incoming tcp packets against listening ports,
; makes new sockets and indicates for which tcp connection the packet is.
; INPUT: none
; OUTPUT: flags2.TCP_SOCK
; ******************************************************************************
		; check if we are waiting for an ARP response.
		; have to Dump & ignore all incoming tcp packets until ARP
		; response received or ARP timed out. The tcp receive functions
		; can respond with sending tcp packets which will overwrite
		; the stalled tcp packet. If we recorded which tcp conn. buffer
		; has a stalled packet, we can accept tcp packets for the other
		; tcp conn. but let's keep it simple now
		jb	flags3.ARP_REQ_SENT, :ignorePacket

		; pre-screening to check if the local port=service is offered
		call	@CheckLocalPortTCB1	; check if for a port
						; we're listening on
		jz	:TCPSocket1		; yes, service is bound
						; to TCB1_BANK
		call	@CheckLocalPortTCB2	; check if for a port
						; we're listening on
		jz	:TCPSocket2		; yes, service is bound
						; to TCB2_BANK
		jmp	:ignorePacket		; no, we Don't offer the service

:TCPSocket1	call	@CheckSocket1IP		; check if remote IP
						; is in socket1
		jnz	:CreateTCPSocket1	; no, make a new conn./socket
		call	@CheckRemotePortTCB1	; yes, check if remote port
						; is in TCB1_BANK
		jz	:TCPConnection1		; yes, packet is for tcp conn. 1
		jmp	:CreateTCPSocket1	; no, but make a new conn.
						; if we're listening

:TCPSocket2	call	@CheckSocket2IP		; check if remote IP
						; is in socket2
		jnz	:CreateTCPSocket2	; no, make a new conn./socket
		call	@CheckRemotePortTCB2	; yes, check if remote port
						; is in TCB2_BANK
		jz	:TCPConnection2		; yes, packet is for tcp conn. 2
		jmp	:CreateTCPSocket2	; no, but make a new conn.
						; if we're listening

:CreateTCPSocket1
		; we may only create a socket when tcp1 connection is in
		; LISTEN state
		_bank	TCP_BANK
		cjne	tcp1State, #TCP_ST_LISTEN, :ignorePacket

		; create tcp1 socket
		call	@CopyRemoteIPSocket1	; store remote IP in socket1
		call	@CopyRemotePortTCB1	; store remote port in socket1
		jmp	:TCPConnection1

:CreateTCPSocket2
		; we may only create a socket when tcp2 connection is in
		; LISTEN state
		_bank	TCP_BANK
		cjne	tcp2State, #TCP_ST_LISTEN, :ignorePacket

		; create tcp2 socket
		call	@CopyRemoteIPSocket2	; store remote IP in socket2
		call	@CopyRemotePortTCB2	; store remote port in socket2
		jmp	:TCPConnection2

:TCPConnection1	clrb	flags2.TCP_SOCK		; indicate tcp packet is for tcp1
		clz				; indicate packet is ok
		retp

:TCPConnection2	setb	flags2.TCP_SOCK		; indicate tcp packet is for tcp2
		clz				; indicate packet is ok
		retp

:ignorePacket	call	@NICDumpRxFrame		; discard the tcp packet
		stz				; indicate packet is not ok -
						; don't process it
		retp

; ******************************************************************************
_TCPTransmit
; See if the application has any Data to transmit. If there are no outstanding
; packets and the application has Data, then transmit a packet.
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		_bank	TCP_BANK		; _bank cus we called
						; TCPAppInit priorly

		; check which tcp connection has chance to tx
		jb	flags2.TCP_TXSEMA, :tcp2Tx

		; tcp1 has tx chance
		cjae	tcp1State, #TCP_ST_ESTABED, :ok1	; is tcp1
								; connection estab?
		retp				; exit, tcp1 connection
						; not established

		; yes, tcp1 is est.
:ok1		test	tcp1UnAckMSB		; are there any bytes
						; unacknowledged?
		jnz	:timeout1
		test	tcp1UnAckLSB
		jnz	:timeout1
		cje	tcp1State, #TCP_ST_FINWAIT1, :finTimeout	; check for
									; FIN timeout
		jmp	:askAppTxData

		; tcp2 has tx chance
:tcp2Tx		cjae	tcp2State, #TCP_ST_ESTABED, :ok2	; is tcp2
								; connection estab?
		retp				; exit, tcp2 connection
						; not established

		; yes, tcp2 is est.
:ok2		test	tcp2UnAckMSB		; are there any bytes
						; unacknowledged?
		jnz	:timeout2
		test	tcp2UnAckLSB
		jnz	:timeout2
		cje	tcp2State, #TCP_ST_FINWAIT1, :finTimeout	; check for
									; FIN timeout

:askAppTxData	call	@TCPAppTxBytes		; ask the application if
						; it wants to tx
		_bank	TCP_BANK		; _bank cus we called
						; TCPAppTxBytes priorly

		; check if unack bytes from tcp1 or 2
		jb	flags2.TCP_SOCK, :chkAppTxDataCon2

		; tcp1
		mov	w, tcp1UnAckMSB
		or	w, tcp1UnAckLSB
		jnz	:appHasTxData
		retp	; nope, it Doesn't; so we're Done here then

		; tcp2
:chkAppTxDataCon2
		mov	w, tcp2UnAckMSB
		or	w, tcp2UnAckLSB
		jnz	:appHasTxData
		retp	; nope, it Doesn't; so we're Done here then

:appHasTxData	; start a TCP packet

		; check if for tcp1 or 2
		jb	flags2.TCP_SOCK, :loadUnackTcp2

		; tcp1
		mov	tcpLengthLSB, tcp1UnAckLSB	; tcpLength =
							; # bytes to transmit
		mov	tcpLengthMSB, tcp1UnAckMSB
		jmp	:contAppHasTxData

		; tcp2
	;	tcpLength = # bytes to transmit
:loadUnackTcp2	mov	tcpLengthLSB, tcp2UnAckLSB
		mov	tcpLengthMSB, tcp2UnAckMSB

:contAppHasTxData
		mov	globTemp1, #(tcb1Flags-TCB1_BANK)
		; set pointer to correct tcb for current tcp connection
		call	@SetTCBPointer
		mov	indf, #((1<<TCP_FLAG_PSH)|(1<<TCP_FLAG_ACK))
		call	@TCPCheckSumInit
		call	@TCPStartPktOut		; send the header

		; insert the packet Data while computing the checksum
		; over the Data
		bank	TCP_BANK
		; check if copy for tcp1 or 2
		jb	flags2.TCP_SOCK, :cpyUnAckTCP2

		; tcp1
		mov	tcpTmpLSB, tcp1UnAckLSB
		mov	tcpTmpMSB, tcp1UnAckMSB
		jmp	:dataLoopBgn

		; tcp2
:cpyUnAckTCP2	mov	tcpTmpLSB, tcp2UnAckLSB
		mov	tcpTmpMSB, tcp2UnAckMSB

:dataLoopBgn	inc	tcpTmpMSB		; so that we can loop easier later
:dataLoop	call	@TCPAppTxData
		_banky	TCP_BANK		; cus we called TCPAppTxData
						; priorly
		call	@NICWriteAgain		; which Doesn't clobber w,
						; which is nice
		call	@TCPCheckSumAcc		; accumulate the checksum
		decsz	tcpTmpLSB
		jmp	:dataLoop
		decsz	tcpTmpMSB
		jmp	:dataLoop

		; now go back and fill in the TCP checksum
		bank	NIC_BANK

		; use correct tx buffer for current tcp connection
		sb	flags2.TCP_SOCK
		mov	w, #TXBUF2_START
		snb	flags2.TCP_SOCK
		mov	w, #TXBUF3_START

		mov	nicCopySrcMSB, w
		mov	nicCopySrcLSB, #(6+6+2+20+16)	; TCP <checksum> (MSB)
		bank	TCP_BANK
		mov	w, /tcpCheckSumMSB
		call	@NICBufWrite
		bank	NIC_BANK
		inc	nicCopySrcLSB
		bank	TCP_BANK
		mov	w, /tcpCheckSumLSB
		call	@NICBufWrite

		call	@NICSendTxFrame		; end and send the packet

		bank	TIMER_BANK		; initialise the restart timer

		; check if tcp1 or 2 timer to be cleared
		jb	flags2.TCP_SOCK, :initTcp2Timer

		; tcp1
		clr	tcp1TimerMSB
		clr	tcp1TimerLSB
		retp

		; tcp2
:initTcp2Timer	clr	tcp2TimerMSB
		clr	tcp2TimerLSB
		retp

:finTimeout	bank	TIMER_BANK
		; check if tcp1 or 2 timer to be checked
		jb	flags2.TCP_TXSEMA, :chkClrTcp2Tmr

		; tcp1
		csae	tcp1TimerMSB, #TCP_RESTART_EXP	; has the restart timer
							; expired?
		retp					; no
		clr	tcp1TimerMSB			; yes, initialise the
							; restart timer
		clr	tcp1TimerLSB
		jmp	@TCPSendFin

		; tcp2
:chkClrTcp2Tmr	csae	tcp2TimerMSB, #TCP_RESTART_EXP	; has the timer expired?
		retp					; no
		clr	tcp2TimerMSB			; yes, initialise the
							; restart timer
		clr	tcp2TimerLSB
		jmp	@TCPSendFin

		; check if timed out on waiting for ack on tcp connection1
:timeout1	bank	TIMER_BANK
		csae	tcp1TimerMSB, #TCP_RESTART_EXP	; has the restart timer
							; expired?
		retp					; no
		clr	tcp1TimerMSB			; yes, initialise the
							; restart timer
		clr	tcp1TimerLSB
		clrb	flags2.TCP_SOCK			; indicate conn1.
		jmp	@TCPReTransmit			; transmit the packet
							; again

:timeout2	bank	TIMER_BANK
		csae	tcp2TimerMSB, #TCP_RESTART_EXP	; has the restart timer
							; expired?
		retp					; no
		clr	tcp2TimerMSB			; yes, initialise the
							; restart timer
		clr	tcp2TimerLSB
		setb	flags2.TCP_SOCK			; indicate conn2.
		jmp	@TCPReTransmit			; transmit the packet
							; again

	IF	SMTP
; ******************************************************************************
_smtpCompare220
; Compares the SMTP reply code in memory against 220
; INPUT: smtpReplyCode
; OUTPUT: Z = set if reply code is 220
; ******************************************************************************
		_bank	SMTPREPLY_BANK
		mov	w, #'2'
		xor	w, smtpReplyCode3
		sz
		retp
		mov	w, #'2'
		xor	w, smtpReplyCode2
		sz
		retp
		mov	w, #'0'
		xor	w, smtpReplyCode1
		retp

; ******************************************************************************
_smtpCompare221
; Compares the SMTP reply code in memory against 221
; INPUT: smtpReplyCode
; OUTPUT: Z = set if reply code is 220
; ******************************************************************************
		_bank	SMTPREPLY_BANK
		mov	w, #'2'
		xor	w, smtpReplyCode3
		sz
		retp
		mov	w, #'2'
		xor	w, smtpReplyCode2
		sz
		retp
		mov	w, #'1'
		xor	w, smtpReplyCode1
		retp

; ******************************************************************************
_smtpCompare250
; Compares the SMTP reply code in memory against 250
; INPUT: smtpReplyCode
; OUTPUT: Z = set if reply code is 220
; ******************************************************************************
		_bank	SMTPREPLY_BANK
		mov	w, #'2'
		xor	w, smtpReplyCode3
		sz
		retp
		mov	w, #'5'
		xor	w, smtpReplyCode2
		sz
		retp
		mov	w, #'0'
		xor	w, smtpReplyCode1
		retp

; ******************************************************************************
_smtpCompare354
; Compares the SMTP reply code in memory against 354
; INPUT: smtpReplyCode
; OUTPUT: Z = set if reply code is 220
; ******************************************************************************
		_bank	SMTPREPLY_BANK
		mov	w, #'3'
		xor	w, smtpReplyCode3
		sz
		retp
		mov	w, #'5'
		xor	w, smtpReplyCode2
		sz
		retp
		mov	w, #'4'
		xor	w, smtpReplyCode1
		retp
	ENDIF

; ******************************************************************************
_TCPAppRxDone
; This is called following the last call to TCPAppRxData(). It signifies the
; end of the received packet
; [TCP API Function]
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		; jump to current tcp connection code
		jb	flags2.TCP_SOCK, :TCPAppRxDone2

		; tcp1
	IF	SMTP
		_bank	SMTP_BANK
		cje	smtpState, #SMTP_CONNECT, :smtpCon	; check if we
								; connected
		cje	smtpState, #SMTP_HELO,	:smtpHelo	; check if we
								; sent a HELO
		cje	smtpState, #SMTP_MAIL,	:smtpMail	; check if we
								; sent a MAIL FROM
		cje	smtpState, #SMTP_RCPT,	:smtpRcpt	; check if we
								; sent a RCPT TO
		cje	smtpState, #SMTP_DATA,	:smtpData	; check if we
								; sent a DATA
		cje	smtpState, #SMTP_TEXT,	:smtpText	; check if we
								; sent the msg text
		cje	smtpState, #SMTP_QUIT,	:smtpQuit	; check if we
								; sent a QUIT
		retp	; nothing?

		; we have established a tcp conn with a remote smtp
:smtpCon	call	@smtpCompare220		; check if we got 220
		sz
		jmp	:smtpWeQuit		; no, we got something else

		; we got a 220 service ready reply
		jmp	:smtpNxtState

		; we have received a response to our HELO sent.
		; We expect it to be 250
:smtpHelo	call	@smtpCompare250		; check if we got 250
		sz
		jmp	:smtpWeQuit		; no, we got something else

		; we got a 250 requested mail action ok, completed reply
		jmp	:smtpNxtState

		; we have received a response to our MAIL sent.
		; We expect it to be 250
:smtpMail	call	@smtpCompare250		; check if we got 250
		sz
		jmp	:smtpWeQuit		; no, we got something else

		; we got a 250 requested mail action ok, completed reply
		jmp	:smtpNxtState

		; we have received a response to our RCPT sent.
		; We expect it to be 250
:smtpRcpt	call	@smtpCompare250		; check if we got 250
		sz
		jmp	:smtpWeQuit		; no, we got something else

		; we got a 250 requested mail action ok, completed reply
		jmp	:smtpNxtState

		; we have received a response to our DATA sent.
		; We expect it to be 354
:smtpData	call	@smtpCompare354		; check if we got 354
		sz
		jmp	:smtpWeQuit		; no, we got something else

		; we got a 354 start mail input, end with <CRLF>.<CRLF> reply
		jmp	:smtpNxtState

		; we have received a response to the text we sent.
		; We expect it to be 250
:smtpText	call	@smtpCompare250		; check if we got 250
		sz
		jmp	:smtpWeQuit		; no, we got something else

		; we got a 250 message accepted reply
		setb	flags3.SMTP_OK		; indicate mail went through
		jmp	:smtpNxtState

		; we have received a response to the QUIT we sent.
		; We expect it to be 221
:smtpQuit	call	@smtpCompare221		; check if we got 221
		jz	:smtpQuitGood
		_bank	SMTP_BANK
		clr	smtpState
		call	@TCPAppClose		; no, we have to close now or
						; will be here forever
		retp

		; we got a 221 service closing transmission channel reply
:smtpQuitGood	_bank	SMTP_BANK
		clr	smtpState		; change state to CLOSE, remote
						; SMTP will close tcp
		retp

		; we got a response we can't handle.
		; We will send a QUIT to let the remote
		; SMTP Do a tcp close
:smtpWeQuit	_bank	SMTP_BANK
		mov	w, #SMTP_QUIT		; change state to
						; QUIT must be sent
		mov	smtpState, w
		setb	flags2.SMTP_TXEN
		retp

		; go to next SMTP state
:smtpNxtState	_bank	SMTP_BANK
		inc	smtpState
		setb	flags2.SMTP_TXEN	; enable smtp transmit so that
						; a new command
						; or text can be sent
		retp
	ENDIF
		retp

		; tcp2
:TCPAppRxDone2
	IF	HTTP
		; check if end of 2nd packet rcvd from Netscape type browser
		_bank	HTTP_BANK
		jnb	flags2.BROWSER_TYPE, :methodChk
		clrb	flags2.BROWSER_TYPE	; yes, clear flag
		jmp	:getM			; reset other flags				
		
		; check if we rcvd a packet with http post and did not get 'led'
		; field meaning it came from a Netscape browser.
:methodChk	jnb	flags3.HTTP_METHOD, :getM	; skip chk if method get 
		
		; it is post. check if we got the 'led' field in this packet
		cje	httpParseState2, #4, :getM
		setb	flags2.BROWSER_TYPE	; no, netscape type browser				
		clr	httpParseState		; clear for parser in 2nd pkt
				
:getM		clrb	flags3.GOT_HTTP_METHOD	; clear have method for nxt pkt
		clrb	flags3.GOT_URI		
		mov	w, #2			; restore the state of the
		mov	httpParseState,	w	; httpParseState variable we
	ENDIF					; borrowed for the POST parser
		retp

; ******************************************************************************
_TCPApp1Init
; Called repeatedly as long as TCP connection1 state is closed
; [TCP API Function]
; INPUT:  none
; OUTPUT: none
; ******************************************************************************

	IF	SMTP
		; check if this is the first time sw is pressed, or if it is
		; still held in from the first time it was pressed.
		; If email goes through before switch is released this will
		; prevent another.
		jnb	flags2.SW_PRESSED, :firstPressChk

		mov	w, #(1<<SW)	; look if SW1 is pressed
		and	w, SW_PORT
		snz			; no, so it must be released now
		retp			; yes, have to wait until released

		clrb	flags2.SW_PRESSED

		; check if the prev email was accepted by the remote SMTP, so we
		; can try again.
		jnb	flags3.SMTP_OK,	:sendMailAgain

		; check here if we want to send email.
:firstPressChk	mov	w, #(1<<SW)	; look if SW1 is pressed
		and	w, SW_PORT
		sz			; yes
		retp			; no, just return

		setb	flags2.SW_PRESSED	; indicate switch is pressed

		; yes, so we need to send an email now or again
:sendMailAgain	clrb	flags3.SMTP_OK		; clear the 'mail went through' indicator
						; before we send it
		_bank	SMTP_BANK
		mov	w, #SMTP_CONNECT	; start SMTP state machine to connect
		mov	smtpState, w

		; set up local & remote ports for tcp1 connection
		_bank	TCB1_BANK
		mov	tcb1LocalPortLSB, #100
		mov	tcb1LocalPortMSB, #100
		mov	tcb1RemotePortLSB, #SMTP_PORT_LSB
		mov	tcb1RemotePortMSB, #SMTP_PORT_MSB

		; fill in remote IP to connect with in tcp socket
		_bank	TCPSOCKET_BANK
		mov	sock1RemoteIP3,	#SMTP_SERVER_IP3
		mov	sock1RemoteIP2,	#SMTP_SERVER_IP2
		mov	sock1RemoteIP1,	#SMTP_SERVER_IP1
		mov	sock1RemoteIP0,	#SMTP_SERVER_IP0

		; indicate tcp1 connection
		clrb	flags2.TCP_SOCK
		jmp	@TCPAppActiveOpen	; open a tcp connection on socket1
	ENDIF
		retp


	ORG	$E00	; Page7

TCPApp2Init	jmp	_TCPApp2Init
DeleteSocket1	jmp	_DeleteSocket1
DeleteSocket2	jmp	_DeleteSocket2

	IF	HTTP
; jump table can be moved with all E2 functions if neccessary.
E2Delay600ns	jmp	_E2Delay600ns
E2Delay900ns	jmp	_E2Delay900ns
E2Delay1300ns	jmp	_E2Delay1300ns
E2SDAInput	jmp	_E2SDAInput
E2SDAOutputHi	jmp	_E2SDAOutputHi
E2SDAOutputLo	jmp	_E2SDAOutputLo
E2GenStartCond	jmp	_E2GenStartCond
E2GenStopCond	jmp	_E2GenStopCond
E2Write8	jmp	_E2Write8
E2Read8		jmp	_E2Read8
E2Read8Ack	jmp	_E2Read8Ack
E2Read8NoAckStop jmp	_E2Read8NoAckStop
E2RecvAck	jmp	_E2RecvAck
E2SendAck	jmp	_E2SendAck
E2SendNotAck	jmp	_E2SendNotAck
E2SendRdCmd	jmp	_E2SendRdCmd
E2SendWrCmd	jmp	_E2SendWrCmd
E2SetAddr	jmp	_E2SetAddr
Bin8ToBCD	jmp	_Bin8ToBCD
BCDToASCII	jmp	_BCDToASCII
	ENDIF

	IF	DHCP
DHCPREQUESTSend		jmp	_DHCPREQUESTSend
DHCPDISCOVERSend	jmp	_DHCPDISCOVERSend
DHCPSendCommon2		jmp	_DHCPSendCommon2
UDPProcBcstPktIn	jmp	_UDPProcBcstPktIn
	ENDIF

; ******************************************************************************
NICReadAgain_7
; Shortform for calling NICReadAgain(), which is in Page1 (This is in Page7)
; ******************************************************************************
		jmp	@NICReadAgain

; ******************************************************************************
NICWriteAgain_7
; Shortform for calling NICWriteAgain(), which is in Page1 (This is in Page7)
; ******************************************************************************
		jmp	@NICWriteAgain

; ******************************************************************************
NICDumpRxFrame_7
; Shortform for calling NICDumpRxFrame(), which is in Page1 (This is in Page7)
; ******************************************************************************
		jmp	@NICDumpRxFrame

; ******************************************************************************
UDPEndPktOut
; Wraps up and transmits the UDP packet
; [UDP API Function]
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		_bank	NIC_BANK
		jmp	@NICSendTxFrameNow

; ******************************************************************************
UDPAppInit
; Application UDP Initialization code (Example)
; This function is called automatically once by the stack During startup
; [UDP API Function]
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		_bank	 UDP_BANK
		mov	udpRxDestPortMSB, #UDP_RX_DEST_MSB
		mov	udpRxDestPortLSB, #UDP_RX_DEST_LSB
		retp

; ******************************************************************************
UDPAppProcPktIn
; Application Incoming UDP packet handler (Example)
; This function is called whenever an application (matches udpRxDestPortxSB)
; packet is received. The appplication can call NICReadAgain() to extract
; sequentially extract each byte of the <data> field in the UDP packet.
; [UDP API Function]
; INPUT:  {udpRxDataLenMSB,udpRxDataLenLSB} = number of bytes in UDP <data>
;	  {udpRxSrcPortMSB,udpRxSrcPortLSB} = UDP <source_port>
; OUTPUT: none
; ******************************************************************************									
		call	NICReadAgain_7
		and	w, #%01000000
		xor	ra, w		; toggle I/O pins
		_bank	UDP_BANK
		clr	udpTxSrcPortMSB
		clr	udpTxSrcPortLSB

		mov	udpTxDestPortMSB, udpRxSrcPortMSB
		mov	udpTxDestPortLSB, udpRxSrcPortLSB

		clr	udpTxDataLenMSB		; send 2 bytes of data
		mov	udpTxDataLenLSB, #2	;

		call	UDPStartPktOut

		mov	w, ra		; send new port state
		call	NICWriteAgain_7
		mov	w, #$00		; one-byte padding
		call	NICWriteAgain_7

		jmp	UDPEndPktOut

; ******************************************************************************
UDPStartPktOut
; Starts an outgoing UDP packet by constructing an IP and UDP packet header
; [UDP API Function]
; INPUT:  {remoteIP0-3} = Destination IP addr for UDP pkt
;	  {udpTxSrcPortMSB,udpTxSrcPortLSB} = UDP Source Port
;	  {udpTxDestPortMSB,udpTxDestPortLSB} = UDP Destination Port
;	  {udpTxDataLenMSB,udpTxDataLenLSB} = UDP Data Length (just Data)
; OUTPUT: none
; ******************************************************************************
		; compute IP <total_length>
		_bank	UDP_BANK
		mov	w, udpTxDataLenLSB
		_bank	IP_BANK
		mov	ipLengthLSB, w
		_bank	UDP_BANK
		mov	w, udpTxDataLenMSB
		_bank	IP_BANK
		mov	ipLengthMSB, w
		add	ipLengthLSB, #(20+8)	; add in size of UDP hdr (8)
						; and IP hdr (20)
		snc
		inc	ipLengthMSB

		; update IP <identifier>
		inc	ipIdentLSB
		snz
		inc	ipIdentMSB

		; set IP <protocol> for UDP
		mov	ipProtocol, #17

		; compute IP <header_checksum>
		call	@IPGenCheckSum

		; now we're ready to construct the IP header
		call	@IPStartPktOut

		; then construct the UDP header

		_bank	UDP_BANK

		; UDP <source_port>
		mov	w, udpTxSrcPortMSB
		call	NICWriteAgain_7
		mov	w, udpTxSrcPortLSB
		call	NICWriteAgain_7

		; UDP <destination_port>
		mov	w, udpTxDestPortMSB
		call	NICWriteAgain_7
		mov	w, udpTxDestPortLSB
		call	NICWriteAgain_7

		; UDP <length>
		mov	w, #8
		add	w, udpTxDataLenLSB
		mov	w, udpTxDataLenMSB
		snc
		inc	wreg
		call	NICWriteAgain_7
		mov	w, #8
		add	w, udpTxDataLenLSB
		call	NICWriteAgain_7

		; UDP <checksum> = 0
		mov	w, #$0
		call	NICWriteAgain_7
		jmp	NICWriteAgain_7

; ******************************************************************************
UDPProcPktIn
; Processes an Incoming UDP packet
; INPUT:  nicCurrPktPtr = points to beginning of received packet
; OUTPUT: none
; ******************************************************************************					
		_bank	UDP_BANK

		; UDP <source_port>
		call	NICReadAgain_7
		mov	udpRxSrcPortMSB, w
		call	NICReadAgain_7
		mov	udpRxSrcPortLSB, w

		; UDP <destination_port>
		call	NICReadAgain_7
		xor	w, udpRxDestPortMSB
		jnz	:outtaHere
		call	NICReadAgain_7
		xor	w, udpRxDestPortLSB
		jnz	:outtaHere

		; UDP <message_length>
		call	NICReadAgain_7
		mov	udpRxDataLenMSB, w
		call	NICReadAgain_7
		mov	udpRxDataLenLSB, w

		; ignore UDP <checksum>
	REPT	2
		call	NICReadAgain_7
	ENDR

		; UDP <data>
	IF	DHCP
		snb	flags.RX_IS_IP_BCST
		call	UDPProcBcstPktIn
	
                ; prevent UDP API func call when in config
		_bank	DHCP_BANK
		jb	dhcpFlags.DHCP_CONFIG, :outtaHere
	ENDIF
		
		sb	flags.RX_IS_IP_BCST
		call	UDPAppProcPktIn		

:outtaHere	_bank	IP_BANK
		jmp	NICDumpRxFrame_7

	IF	DHCP
; ******************************************************************************
CheckIPLeaseExpire
; Checks if our IP lease has expired. If it has, renew it. If we never got a
; lease in the first place, just return.
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		sb	flags2.GOT_IP_LEASE	; test if we got an IP lease
		retp				; return, we never got an IP lease

		; we got an IP lease previously
		; check if it is expiring. We're comparing seconds against
		; seconds here
		_bank	DHCP_BANK
		mov	w, dhcpIPLeaseTm3
		xor	w, dhcpTimer3
		jnz	:outtaLeaseChk
		mov	w, dhcpIPLeaseTm2
		xor	w, dhcpTimer2
		jnz	:outtaLeaseChk
		mov	w, dhcpIPLeaseTm1
		xor	w, dhcpTimer1
		jnz	:outtaLeaseChk
		mov	w, dhcpIPLeaseTm0
		xor	w, dhcpTimer0
		jnz	:outtaLeaseChk

		; Lease has expired, renew it
		clrb	flags2.GOT_IP_LEASE	; reset IP lease.
		setb	flags2.RENEW_IP_LEASE	; indicate this is a lease
						; renewal
		jmp	DHCPConfig		; start the DHCP configuration

:outtaLeaseChk	retp

; ******************************************************************************
DHCPConfig
; This function uses DHCP, Dynamic Host Configuration Protocol, to configure the
; iSX. The result is an assigned IP address for a certain time.
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		_bank	DHCP_BANK
		setb	dhcpFlags.DHCP_CONFIG	; indicate DHCP config in progress
		
		; initialize UDP receive port for DHCP
		_bank	UDP_BANK
		clr	udpRxDestPortMSB	; DHCP(BOOTP) Client (Port 68)
		mov	udpRxDestPortLSB, #68	;

		; DHCPConfig will be called again and again for IP lease
		; renewals. Check if this is a renewal or a first entry from
		; startup
		jb	flags2.RENEW_IP_LEASE,	:leaseRenew

		; Send a DHCP Discovery message
:dhcpDiscSend	call	DHCPDISCOVERSend	; send DHCPDISCOVER message
		_bank	DHCP_BANK
		clr	dhcpTimer0		; initialize receive timeout timer

		; Wait with timeout for an incoming frame
		; on each timeout, a new Discovery will be sent until max tries
		; reached
:dhcpWaitOffer	call	@NICWaitRxFrame		; check if we received something
		jb	flags2.GOT_RX_FRAME, :dhcpGotRxFrame	; check if we
								; got something
		_bank	DHCP_BANK
		cje	dhcpTimer0, #DHCP_DISC_EXP, :outtaDHCP	; check if
								; timeout - Disc
		jmp	:dhcpWaitOffer	; not timed out yet, wait some more

		; Check if rcvd frame is UDP (DHCP carrier).
		; If it is UDP, process contents, else go back to
		; waiting for an incoming frame.
:dhcpGotRxFrame	call	@CheckIPDatagram
		jnb	flags.RX_IS_UDP, :dhcpWaitOffer
		clrb	flags.RX_IS_UDP
		call	UDPProcPktIn
		sb	flags.GOT_DHCP_OFFER
		jmp	:dhcpWaitOffer

		; If we got to here, we rcvd a valid DHCP offer.
		; Send a DHCP request message
:leaseRenew	clr	globTemp3		; initialize re-transmit counter

:dhcpReqSend	cje	globTemp3, #DHCP_REQ_TRIES, :outtaDHCP ; tried enough?
		_bank	DHCP_BANK
		clr	dhcpTimer0
		call	DHCPREQUESTSend
		inc	globTemp3

		; Wait with timeout for an incoming frame
		; on each timeout, a new request will be sent
		; until max tries reached
:dhcpWaitAck	call	@NICWaitRxFrame
		jb	flags2.GOT_RX_FRAME, :dhcpGotAck
		_bank	DHCP_BANK
		cje	dhcpTimer0, #DHCP_REQ_EXP, :dhcpReqSend
		jmp	:dhcpWaitAck		; wait some more

		; Check if rcvd frame is UDP (DHCP carrier).
		; If it is UDP, process contents, else go back to waiting
		; for an incoming frame.
:dhcpGotAck	call	@CheckIPDatagram	; check packet type
		jnb	flags.RX_IS_UDP, :dhcpWaitAck	; if not UDP
							; go back to waiting
		clrb	flags.RX_IS_UDP
		call	UDPProcPktIn		; is UDP, process contents
		sb	flags2.GOT_IP_LEASE	; check if we got an IP lease
		jmp	:dhcpWaitAck		; no wait some more

		; reset 1sec renewal timers
		_bank	DHCP_BANK
		clr	dhcpBaseTimer0
		clr	dhcpBaseTimer1
		clr	dhcpTimer0
		clr	dhcpTimer1
		clr	dhcpTimer2
		clr	dhcpTimer3

:outtaDHCP	clrb	dhcpFlags.DHCP_CONFIG	; reset
		call	@UDPAppInit	; restore the user UDP application port
                
                ; check if successful, otherwise rebind
		jb	flags2.GOT_IP_LEASE, :dhcpConfigExit
		jmp	@Main			; rebind

		; success, exit normally
:dhcpConfigExit	clrb	flags2.RENEW_IP_LEASE	; reset IP lease renewal		
		retp

; ******************************************************************************
DHCPSendCommon1
; Helper function for DHCPDISCOVERSend and DHCPREQUESTSend
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		; set ethernet addr to broadcast addr
		_bank	NIC_BANK
		mov	w, #$FF
		mov	nicRemoteEth0, w
		mov	nicRemoteEth1, w
		mov	nicRemoteEth2, w
		mov	nicRemoteEth3, w
		mov	nicRemoteEth4, w
		mov	nicRemoteEth5, w

		; set IP addr to broadcast addr
		bank	IP_BANK
		mov	w, #$FF
		mov	remoteIP3, w
		mov	remoteIP2, w
		mov	remoteIP1, w
		mov	remoteIP0, w

		_bank	UDP_BANK

		clr	udpTxSrcPortMSB		; DHCP client
		mov	udpTxSrcPortLSB, #68	;

		clr	udpTxDestPortMSB	; DHCP server
		mov	udpTxDestPortLSB, #67	;

		call	@UDPStartPktOut

		; <op>
		mov	w, #1
		call	NICWriteAgain_7
		; <htype>
		mov	w, #1
		call	NICWriteAgain_7
		; <hlen>
		mov	w, #6
		call	NICWriteAgain_7
		; <hops>
		mov	w, #0
		call	NICWriteAgain_7
		; <transaction_id> = 0xABABABAB
		mov	w, #$AB
	REPT	4
		call	NICWriteAgain_7
	ENDR
		; <seconds> = 256
		mov	w, #1
	REPT	2
		call	NICWriteAgain_7
	ENDR
		; <flags>
		mov	w, #$80
		call	NICWriteAgain_7
		mov	w, #0
		jmp	NICWriteAgain_7

; ******************************************************************************
_DHCPSendCommon2
; Helper function for DHCPDISCOVERSend and DHCPREQUESTSend
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		; <client_hw_addr>
		mov	w, #SX_ETH_ADDR0
		call	NICWriteAgain_7
		mov	w, #SX_ETH_ADDR1
		call	NICWriteAgain_7
		mov	w, #SX_ETH_ADDR2
		call	NICWriteAgain_7
		mov	w, #SX_ETH_ADDR3
		call	NICWriteAgain_7
		mov	w, #SX_ETH_ADDR4
		call	NICWriteAgain_7
		mov	w, #SX_ETH_ADDR5
		call	NICWriteAgain_7
		; <client_hw_addr>,<server_host_name>,<boot_filename>
		mov	globTemp1, #(10+64+128)
		mov	w, #0
:loop2		call	NICWriteAgain_7
		decsz	globTemp1
		jmp	:loop2
		; <option_magic_cookie>
		mov	w, #99
		call	NICWriteAgain_7
		mov	w, #130
		call	NICWriteAgain_7
		mov	w, #83
		call	NICWriteAgain_7
		mov	w, #99
		jmp	NICWriteAgain_7

; ******************************************************************************
_DHCPDISCOVERSend
; Send DHCPDISCOVER message
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		_bank	UDP_BANK
		clr	udpTxDataLenMSB
		mov	udpTxDataLenLSB, #(240+3+0+1)	; without req-IP option
		;mov	udpTxDataLenLSB, #(240+3+6+1)	; with req-IP option

		call	DHCPSendCommon1

		; <client_IP>, <your_IP>,<server_IP>,<router_IP> = 0
		mov	globTemp1, #(4+4+4+4)
		mov	w, #0
:loop1		call	NICWriteAgain_7
		decsz	globTemp1
		jmp	:loop1

		call	DHCPSendCommon2

		; <option-message_type>
		mov	w, #53
		call	NICWriteAgain_7
		mov	w, #1
		call	NICWriteAgain_7
		mov	w, #1			; DHCPDISCOVER
		call	NICWriteAgain_7

		; <option-requested_IP> -- optional
		;mov w, #50
		;call NICWriteAgain_7
		;mov w, #4
		;call NICWriteAgain_7
		;mov w, #SX_IP_ADDR3
		;call NICWriteAgain_7
		;mov w, #SX_IP_ADDR2
		;call NICWriteAgain_7
		;mov w, #SX_IP_ADDR1
		;call NICWriteAgain_7
		;mov w, #SX_IP_ADDR0
		;call NICWriteAgain_7

		; <option-end_option> -- not optional
		mov	w, #255
		call	NICWriteAgain_7

		; and ... that should Do it!
		jmp	UDPEndPktOut

; ******************************************************************************
_DHCPREQUESTSend
; Send DHCPREQUEST message
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		_bank	UDP_BANK
		mov	udpTxDataLenMSB, #((240+3+6+6+1)>>8)
		mov	udpTxDataLenLSB, #((240+3+6+6+1)&$FF)

		call	DHCPSendCommon1

		; <client_IP>
		_bank	IP_BANK
		mov	w, myIP3
		call	NICWriteAgain_7
		mov	w, myIP2
		call	NICWriteAgain_7
		mov	w, myIP1
		call	NICWriteAgain_7
		mov	w, myIP0
		call	NICWriteAgain_7

		; <your_IP>,<server_IP>,<router_IP> = 0
		mov	globTemp1, #(4+4+4)
		mov	w, #0
:loop1		call	NICWriteAgain_7
		decsz	globTemp1
		jmp	:loop1

		call	DHCPSendCommon2

		; <option-message_type>
		mov	w, #53
		call	NICWriteAgain_7
		mov	w, #1
		call	NICWriteAgain_7
		mov	w, #3			; DHCPREQUEST
		call	NICWriteAgain_7

		; <option-server_id>
		mov	w, #54			; option server identifier
		call	NICWriteAgain_7
		mov	w, #4			; length
		call	NICWriteAgain_7
		_bank	DHCP_BANK
		mov	w, dhcpServerId3
		call	NICWriteAgain_7
		mov	w, dhcpServerId2
		call	NICWriteAgain_7
		mov	w, dhcpServerId1
		call	NICWriteAgain_7
		mov	w, dhcpServerId0
		call	NICWriteAgain_7

		; <option-requested_IP> -- not optional
		mov	w, #50
		call	NICWriteAgain_7
		mov	w, #4
		call	NICWriteAgain_7
		_bank	IP_BANK
		mov	w, myIP3
		call	NICWriteAgain_7
		mov	w, myIP2
		call	NICWriteAgain_7
		mov	w, myIP1
		call	NICWriteAgain_7
		mov	w, myIP0
		call	NICWriteAgain_7

		; <option-end_option> -- not optional
		mov	w, #255
		call	NICWriteAgain_7

		; and ... that should do it!

		jmp	UDPEndPktOut

; ******************************************************************************
_UDPProcBcstPktIn
; The only kind of broadcast UDP packets accepted are DHCP messages: DHCPOFFER
; and DHCPACK
; INPUT:  none
; OUTPUT: none
; ******************************************************************************								
		call	NICReadAgain_7
		xor	w, #2			; check <op> = BOOTP reply
		jnz	:outtaHere
		call	NICReadAgain_7
		xor	w, #1			; check <htype> = 1
		jnz	:outtaHere
		call	NICReadAgain_7
		xor	w, #6			; check <hlen> = 6
		jnz	:outtaHere
		; ignore <hops>
		call	NICReadAgain_7
		; check <transaction_id> = 0xABABABAB
	REPT	4
		call	NICReadAgain_7
		xor	w, #$AB
		jnz	:outtaHere
	ENDR
		; ignore <seconds>, <flags>, <client_IP>
		mov	globTemp1, #(2+2+4)
:loop1		call	NICReadAgain_7
		decsz	globTemp1
		jmp	:loop1

		; record <your_IP>
		_bank	IP_BANK
		call	NICReadAgain_7
		mov	myIP3, w
		call	NICReadAgain_7
		mov	myIP2, w
		call	NICReadAgain_7
		mov	myIP1, w
		call	NICReadAgain_7
		mov	myIP0, w
		; check if it is non-zero
		mov	w, myIP3
		or	w, myIP2
		or	w, myIP1
		or	w, myIP0
		jz	:outtaHere
		; skip <server_IP>, <router_IP>, <client_hw_addr>,
		; <sever_host_name>, <boot_filename>, <option_magic_cookie>
		mov	globTemp1, #(4+4+16+64+128+4)
:loop2		call	@NICPseudoRead
		decsz	globTemp1
		jmp	:loop2
		; <option-message_type>
		call	NICReadAgain_7
		xor	w, #53			; DHCP Message Type
		jnz	:outtaHere
		call	NICReadAgain_7
		xor	w, #1			; length
		jnz	:outtaHere
		call	NICReadAgain_7
		xor	w, #2			; DHCPOFFER
		snz
		setb	flags.GOT_DHCP_OFFER
		xor	w, #2			; get back value in w
		xor	w, #5			; DHCPACK
		jnz	:loop4
		setb	flags.GOT_IP_ADDR	; indicate we got
						; assigned an IP addr
		setb	flags2.GOT_IP_LEASE

		; now search for that Dang(!) <option-server_id>
:loop4		call	NICReadAgain_7
		xor	w, #54			; Server Identifier
		jz	:foundServId

		xor	w, #54			; restore value
		xor	w, #58			; check for T1 (renewal time)
		jnz	:more01			; go to routine to read and discard
		call	NICReadAgain_7
		xor	w, #4			; length
		jnz	:outtaHere
		_bank	DHCP_BANK		; record T1
		call	NICReadAgain_7
		mov	dhcpIPLeaseTm3,	w
		call	NICReadAgain_7
		mov	dhcpIPLeaseTm2,	w
		call	NICReadAgain_7
		mov	dhcpIPLeaseTm1,	w
		call	NICReadAgain_7
		mov	dhcpIPLeaseTm0,	w
		jmp	:loop4
:more01
		call	NICReadAgain_7		; length
		mov	globTemp1, w
:loop3		call	@NICPseudoRead		; read Data but ignore
		decsz	globTemp1		; read as many times as length
		jmp	:loop3
		jmp	:loop4

:foundServId	call	NICReadAgain_7		; ignore length
		_bank	DHCP_BANK
		call	NICReadAgain_7
		mov	dhcpServerId3, w
		call	NICReadAgain_7
		mov	dhcpServerId2, w
		call	NICReadAgain_7
		mov	dhcpServerId1, w
		call	NICReadAgain_7
		mov	dhcpServerId0, w

:outtaHere	retp
	ENDIF

	IF	HTTP
; ******************************************************************************
_E2Delay600ns
;	Delay 600ns
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		mov	w, #6
:loop		decsz	wreg
		jmp	:loop
		retp

; ******************************************************************************
_E2Delay900ns
;	Delay 900ns
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		mov	w, #8
:loop		decsz	wreg
		jmp	:loop
		retp

; ******************************************************************************
_E2Delay1300ns
;	Delay 1300ns
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		mov	w, #13
:loop		decsz	wreg
		jmp	:loop
		retp

; ******************************************************************************
_E2SDAInput
_E2SDAOutputHi
; Set SDA as input
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		mov	!E2_PORT, #E2_DDR_SDA_IN
		retp

; ******************************************************************************
_E2SDAOutputLo
; Set SDA as output-low
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		clrb	E2SDA_PIN
		mov	!E2_PORT, #E2_DDR_SDA_OUT
		retp

; ******************************************************************************
_E2GenStartCond
; Generate START condition
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		call	E2SDAOutputHi
		setb	E2SCL_PIN
		call	E2Delay600ns
		call	E2SDAOutputLo
		call	E2Delay600ns
		clrb	E2SCL_PIN
		call	E2Delay600ns
		retp

; ******************************************************************************
_E2GenStopCond
; Generate STOP condition
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		call	E2SDAOutputLo
		setb	E2SCL_PIN
		call	E2Delay600ns
		call	E2SDAOutputHi
		call	E2Delay1300ns
		retp

; ******************************************************************************
_E2Write8
; Write 8 bits out the I2C bus
; INPUT:  w = Data to write
; OUTPUT: none
; ******************************************************************************
		mov	globTemp1, w	;	Data buffer
		mov	globTemp2, #8	; bit counter
:loop		call	E2Delay900ns
		sb	globTemp1.7
		call	E2SDAOutputLo
		snb	globTemp1.7
		call	E2SDAOutputHi
		call	E2Delay900ns
		setb	E2SCL_PIN
		call	E2Delay600ns
		clrb	E2SCL_PIN
		rl	globTemp1
		decsz	globTemp2
		jmp	:loop
		retp

; ******************************************************************************
_E2Read8
; Read 8 bits from the I2C bus
; INPUT:  none
; OUTPUT: w = Data read
; ******************************************************************************
		call	E2SDAInput
		mov	globTemp2, #8	; bit counter
:loop		call	E2Delay900ns
		sb	E2SDA_PIN
		clc
		snb	E2SDA_PIN
		stc
		rl	globTemp1
		setb	E2SCL_PIN
		call	E2Delay600ns
		clrb	E2SCL_PIN
		call	E2Delay900ns
		decsz	globTemp2
		jmp	:loop
		mov	w, globTemp1
		retp

; ******************************************************************************
_E2Read8Ack
; Read 8 bits from the I2C bus and send ACK
; INPUT:  none
; OUTPUT: w = Data read
; ******************************************************************************
		call	E2Read8
		mov	globTemp1, w
		call	E2SendAck
		mov	w, globTemp1
		retp

; ******************************************************************************
_E2Read8NoAckStop
; Read 8 bits from the I2C bus and send a no-ACK and stop-condition
; (terminates sequential read mode on EEPROM)
; INPUT:  none
; OUTPUT: w = Data read
; ******************************************************************************
		call	E2Read8
		mov	globTemp1, w
		call	E2SendNotAck
		call	E2GenStopCond
		mov	w, globTemp1
		retp

; ******************************************************************************
_E2RecvAck
; Receive ACK bit from I2C receiver
; INPUT:  none
; OUTPUT: z: 1 = received ACK, 0 = Didn't receive ACK
; ******************************************************************************
		call	E2SDAInput
		call	E2Delay900ns
		setb	E2SCL_PIN
		sb	E2SDA_PIN
		stz
		snb	E2SDA_PIN
		clz
		call	E2Delay600ns
		clrb	E2SCL_PIN
		call	E2Delay900ns
		retp

; ******************************************************************************
_E2SendAck
; Send ACK bit as acknowledge
; INPUT:  none
; OUTPUT: z: 1 = received ACK, 0 = Didn't receive ACK
; ******************************************************************************
		call	E2SDAOutputLo
		call	E2Delay900ns
		setb	E2SCL_PIN
		call	E2Delay600ns
		clrb	E2SCL_PIN
		call	E2Delay900ns
		retp

; ******************************************************************************
_E2SendNotAck
; Send ACK bit as not-acknowledge
; INPUT:  none
; OUTPUT: z: 1 = received ACK, 0 = Didn't receive ACK
; ******************************************************************************
		call	E2SDAOutputHi
		call	E2Delay900ns
		setb	E2SCL_PIN
		call	E2Delay600ns
		clrb	E2SCL_PIN
		call	E2Delay900ns
		retp

; ******************************************************************************
_E2SendRdCmd
; Tell I2C Device we wish to read from it for this transaction
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		call	E2GenStartCond
		mov	w, #E2_CMD_RD
		call	E2Write8
		call	E2RecvAck
		retp

; ******************************************************************************
_E2SendWrCmd
; Tell I2C Device we wish to write to it for this transaction
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		call	E2GenStartCond
		mov	w, #E2_CMD_WR
		call	E2Write8
		call	E2RecvAck
		retp

; ******************************************************************************
_E2SetAddr
; Set address pointer
; INPUT:  {e2AddrMSB, e2AddrLSB} = address to set to
; OUTPUT: none
; ******************************************************************************
		call	E2SendWrCmd
		_bank	EEPROM_BANK
		mov	w, e2AddrMSB
		call	E2Write8
		call	E2RecvAck
		mov	w, e2AddrLSB
		call	E2Write8
		call	E2RecvAck
		retp

; ******************************************************************************
_Bin8ToBCD
; Converts 8-bit binary number to unpacked BCD
; INPUT:  w = binary number to convert
;	  fsr = pointer to MSD (lowest addr) of a 3-byte buffer
; OUTPUT: [fsr] = unpacked BCD
; ******************************************************************************
		clr	indf
		inc	fsr
		clr	indf
		inc	fsr		; LSD
		mov	indf, w

:loopHun	mov	w, #100
		mov	w, indf-w
		jnc	:loopTen
		mov	indf, w
		dec	fsr
		dec	fsr		; MSD
		inc	indf
		inc	fsr
		inc	fsr		; LSD
		jmp	:loopHun

:loopTen	mov	w, #10
		mov	w, indf-w
		sc
		jmp	:exit
		mov	indf, w
		dec	fsr
		inc	indf
		inc	fsr
		jmp	:loopTen

:exit		retp

; ******************************************************************************
_BCDToASCII
; Converts an unpacked BCD number to an ASCII character
; INPUT:  w = unpacked BCD
; OUTPUT: w = ASCII character
; ******************************************************************************
		mov	globTemp1, w
		mov	w, #'0'
		add	w, globTemp1
		retp
	ENDIF

; ******************************************************************************
_TCPApp2Init
; Called repeatedly as long as TCP connection2 state is closed
; [TCP API Function]
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		; set up local port for tcp2 connection
	IF	HTTP
		_bank	TCB2_BANK
		mov	tcb2LocalPortLSB, #HTTP_PORT_LSB
		mov	tcb2LocalPortMSB, #HTTP_PORT_MSB

		bank	HTTP_BANK
		clr	httpParseState
		clr	httpURIHash

		; indicate tcp2 connection
		setb	flags2.TCP_SOCK
		jmp	@TCPAppPassiveOpen
	ENDIF
		retp

; ******************************************************************************
_DeleteSocket1
; Deletes TCP socket1
; INPUT: none
; OUTPUT: sock1RemoteIP3-0
; ******************************************************************************
		_bank	TCPSOCKET_BANK
		clr	sock1RemoteIP3
		clr	sock1RemoteIP2
		clr	sock1RemoteIP1
		clr	sock1RemoteIP0
		retp

; ******************************************************************************
_DeleteSocket2
; Deletes TCP socket1
; INPUT: none
; OUTPUT: sock2RemoteIP3-0
; ******************************************************************************
		_bank	TCPSOCKET_BANK
		clr	sock2RemoteIP3
		clr	sock2RemoteIP2
		clr	sock2RemoteIP1
		clr	sock2RemoteIP0
		retp


; ***********
; *** END ***
; ***********
	END
