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

; ******************************************************************************
; 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
;	    line 150 for important information!
;	 3. If using SX-ISD, you need SASM.EXE V1.45.5 and SXIDE.EXE V1.07.03 or
;	    greater. Pls go to line 150 for important information!
;	 4. The schematics for the board that runs this code is available, pls
;	    contact sales@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 iSX, pls
;	    contact sales@scenix.com . You'll need the Microsoft Java Virtual
;	    Machine Build 3240, or greater (installed by default with IE5).
; ******************************************************************************

; Program Memory Usage Summary (/w DHCP):
; Page | Usage
; -----------------------------
;   0:	 000-0AF, 190-1F1 (53%)
;   1:	 200-3FB (100%)
;   2:	 400-5FD (100%)
;   3:	 600-7FD (100%)
;   4:	 800-96D (71%)
;   5:	 A00-B5B (68%)
;   6:	 C00-D38 (61%)
;   7:	 E00-E25 (7%)

; Data Memory Usage Summary (/w DHCP):
; Bank | Usage
; -----------------------------
;   0:	 - (0%)
;   1:	 0-F (100%)
;   2:	 0-E (94%)
;   3:	 0-F (100%)
;   4:	 0-F (100%)
;   5:	 0-F (100%)
;   6:	 0-A (73%)
;   7:	 0-C (81%)
;   8:	 0-A (73%)
;   9:	 - (0%)
;   A:	 - (0%)
;   B:	 - (0%)
;   C:	 - (0%)
;   D:	 - (0%)
;   E:	 - (0%)
;   F:	 - (0%)

; This program implements an embedded web-server. The server will respond to
; HTTP requests and surfs up the specified web resource stored on an external
; serial EEPROM. The IP address of the webserver is http://10.1.1.20, if
; the DHCP option is disabled (default). In addition, the iSX can be pinged
; ("ping 10.1.1.20") using the standard PC/Unix ping utility. Finally, the iSX
; can also be controlled, using UDP, by the user, using a Java program,
; udpsx.class, available from Scenix.

; How to Setup your PC to talk with the iSX Board
; -----------------------------------------------
; 1. Open a DOS Command Prompt Window
; 2. Type "route print"
; 3. If you see a line like the one below, skip step [4] and [5]
;    Network Address	Netmask		Gateway Address	Interface	Metric
;    10.1.1.0		255.255.255.0		w.x.y.z		w.x.y.z	1
;    (in the above, "w.x.y.z" is any non-zero IP address)
; 4. Find out the IP address of the PC's Ethernet Interface. Type the following
;    command: "ipconfig"
;    Look for the line "IP Address. . .", given under a section called "Ethernet
;    adapter :". This is the Ethernet IP address of the PC.
;    The IP address must not be 0.0.0.0
;    If you don't find a non-zero Ethernet IP address, follow the instructions
;    Given in the section "How to give your PC a Static IP Address" and restart
;    your computer.
; 5. Update the PC's routing table: Issue the following command:
;    "route add 10.1.1.0 mask 255.255.255.0 <PC_ETHERNET_IP_ADDR>"
;    Be sure to substitute the IP address found in step [4] for the field
;    <PC_ETHERNET_IP_ADDR>
; 6. Power-up the iSX board and connect it to the PC using the supplied cross-
;    over cable.
; 7. Ping the iSX to see if it the connection is alive: "ping 10.1.1.20"
;    If you get a "Request timed out" message, the connection was unsucessful;
;    try typing "arp -s 10.1.1.20 00-00-00-00-00-01 <PC_ETHERNET_IP_ADDR>" at the
;    DOS prompt and try pinging the iSX again.
;    If you get a "Reply from 10.1.1.20: ..." message, the connection is alive.
; 8. Start your web-browser (IE5 works best). Type in the following URL:
;    http://10.1.1.20
; 9. If you successfully pinged the iSX board in step [7] but failed to load the
;    web page in step [8], do the following:
;    In IE, go to Tools->Internet Options. Click on the "Use Blank" button, then
;    hit "OK". Exit and restart IE.
;    In Netscape, go to Edit->Preferences. Click on the "Blank Page" radio button,
;    then hit "OK". Exit and restart Netscape.
;    Repeat step [8].
;
; How to give your PC a Static IP Address (optional)
; --------------------------------------------------
; 1. Right-click on the "Network Neighborhood" icon on the Windows Desktop.
;    Select Properties.
; 2. Select the "TCP/IP -> <YOUR_ETHERNET_ADAPTER>" entry from the scroll-box.
;    Click the Properties button.
; 3. Click on the "IP Address" tab. Select "Specify an IP Address". Enter
;    "10.1.1.1" for the "IP Address" (This will be the Ethernet IP address of
;    the PC). Enter "255.255.255.0" for the "Subnet Mask". Click on OK and
;    restart the computer.


;	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


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

; Parallax -- uncomment the following 2 lines if, and only if, using Parallax's SX-Key
	DEVICE	OSCHS2, DRT60MS
	FREQ	48_000_000	; have to debug at freq != resonant freq
; SASM -- uncomment the following line if, and only if, using Advance Transdata's SX-ISD
;	DEVICE	SX52BD, OSCHS2, WDRT60

	RESET	Main
	ID	'iSX_167'


; ***************************
; *** CONDITIONAL DEFINES ***
; ***************************
DHCP		=	0	; 1 = DHCP enabled, 0 = DHCP disabled
CREDENCE	=	0	; 1 = Credence board, 0 = Scenix board


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

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

flags		EQU	GLOBAL_ORG+0	; various flags used by TCP/IP stack
arpFlags	EQU	GLOBAL_ORG+1	; ARP status flags
globTemp1	EQU	GLOBAL_ORG+2	; not preserved across any function
globTemp2	EQU	GLOBAL_ORG+3	; not preserved across any function
globTemp3	EQU	GLOBAL_ORG+4	; preserved across some functions

; *** Bank 0 ***
; (Don't use this bank -- it's bad for your mental health)
	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	;  "

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

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

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	;  "

DHCP_BANK	=	$

dhcpServerId3	DS	1	; DHCP <server_identifier> = IP addr of DHCP server
dhcpServerId2	DS	1	;  "
dhcpServerId1	DS	1	;  "
dhcpServerId0	DS	1	;  "

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

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

tcpState	DS	1	; 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
tcpTmpAck4	DS	1	; TMP.ACK
tcpTmpAck3	DS	1	; temporary information from the received packet
tcpTmpAck2	DS	1
tcpTmpAck1	DS	1
tcpUnAckMSB	DS	1	; number of unacknowledged bytes
tcpUnAckLSB	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 =	tcpUnAckMSB	; number of bytes app wants to transmit
tcpAppTxBytesLSB =	tcpUnAckLSB	;  "
tcpAppRxBytesMSB =	tcpLengthMSB	; number of bytes app will be receiving
tcpAppRxBytesLSB =	tcpLengthLSB	;  "

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

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

; The ordering of these variables is significant. It is the same as the TCP
; header. This simpifies the send-packet code
tcbLocalPortMSB	DS	1	; source port
tcbLocalPortLSB	DS	1	;  "
tcbRemotePortMSB DS	1	; destination port
tcbRemotePortLSB DS	1	;  "
tcbSndUna4	DS	1	; SND.UNA: oldest unacknowledged byte
tcbSndUna3	DS	1	;  "
tcbSndUna2	DS	1	;  "
tcbSndUna1	DS	1	;  "
tcbRcvNxt4	DS	1	; RCV.NXT: next byte to receive
tcbRcvNxt3	DS	1	;  "
tcbRcvNxt2	DS	1	;  "
tcbRcvNxt1	DS	1	;  "
tcbOffset	DS	1	; length of the TCP options
tcbFlags	DS	1	; flags field
tcbSendWinMSB	DS	1	; send window
tcbSendWinLSB	DS	1	;  "
TCB_END		=	$

; *** 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

; *** 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	; "
tcpTimerMSB	DS	1	; TCP-timer count
tcpTimerLSB	DS	1	;  "
connTimerMSB	DS	1	; Connection-timer count
connTimerLSB	DS	1	;  "

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

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 8 ***
	ORG	$80

MISC_BANK	=	$

ledPort		DS	1	; records the state of the led port
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

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

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

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

; *** 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 ***

	IF CREDENCE

RA_DIR		=	%11110011
RA_OUT		=	%00000000
RA_LVL		=	%11111111
RA_PLP		=	%11111111

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		=	%00000010
RD_OUT		=	%00000011
RD_LVL		=	%11111111
RD_PLP		=	%11111100

RE_DIR		=	%00000000
RE_OUT		=	%00000000
RE_LVL		=	%11111111
RE_PLP		=	%11111111

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		=	rd

E2SCL		=	0
E2SDA		=	1
E2SCL_PIN	=	E2_PORT.E2SCL
E2SDA_PIN	=	E2_PORT.E2SDA

LED_PORT	=	re
LED		=	0
LED_PIN		=	LED_PORT.LED

	ELSE

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

	ENDIF

; *** 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

; *** arpFlags ***

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
ARP_BYPASS	=	3	; indicates that the outgoing packet should not be checked

; *** NIC Constants ***

RXBUF_START	=	$40	; 4608 byte receive buffer (3 max-size packets)
RXBUF_END	=	$53	;  "

TXBUF1_START	=	$53	; 1536 byte transmit buffer for ICMP/UDP
TXBUF2_START	=	$59	; 1536 byte transmit buffer for TCP
TXBUF3_START	=	$5F	; 256 byte transmit buffer for ARP

; *** Ethernet Constants ***

SX_ETH_ADDR0	=	0	; SX's Ethernet Phy MAC Address
SX_ETH_ADDR1	=	0	;  "
SX_ETH_ADDR2	=	0	;  "
SX_ETH_ADDR3	=	0	;  "
SX_ETH_ADDR4	=	0	;  "
SX_ETH_ADDR5	=	1	;  "

; *** ARP Constants ***

ARP_TIMEOUT	=	5	; ARP response timeout period. Must be smaller than other timeouts!

; *** IP Constants ***

SX_IP_ADDR3	=	10	; SX's static IP address (if DHCP disabled)
SX_IP_ADDR2	=	1	;  "
SX_IP_ADDR1	=	1	;  "
SX_IP_ADDR0	=	20	;  "

IP_TTL		=	32

; *** 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
TCP_CONN_EXP	=	80	; TCP connection timeout period

; *** HTTP Constants ***

; States for parsing HTTP headers

HTTP_PORT_MSB	=	0	; 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"

; *** 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

	IF CREDENCE
E2_DDR_SDA_IN	=	(RD_DIR | E2_SDA_MASK)		; direction of SDA port when SDA is input
E2_DDR_SDA_OUT	=	(RD_DIR & (~E2_SDA_MASK))	; direction of SDA port when SDA is output
	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
	ENDIF

; *** ADC Constants ***

ADC_PORT	=	re

ADC_OUT		=	7
ADC_IN		=	6

ADC_OUT_PIN	=	ADC_PORT.ADC_OUT
ADC_IN_PIN	=	ADC_PORT.ADC_IN


; **************
; *** 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 software.
	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 software.
	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
		jmp	:timerEnd

:tcpTimer	; TCP-timer (used for TCP re-transmission timeouts)
		incsz	tcpTimerLSB
		jmp	:connTimer
		inc	tcpTimerMSB

:connTimer	; Connection-timer (used for TCP connection timeouts)
		incsz	connTimerLSB
		jmp	:arpTimer
		inc	connTimerMSB

:arpTimer	; ARP-timer (used for ARP response timeouts)
		incsz	arpTimerLSB
		jmp	:timerEnd
		inc	arpTimerMSB
: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 intefere if E2 access in
		; in progress because LED and E2 may share same port
		_bank	HTTP_BANK
		test	httpParseState
		jnz	:ledBlinkerEnd

		bank	TIMER_BANK
		mov	w, tcpTimerMSB
		and	w, #%00001111
		jnz	:ledHere
		_bank	MISC_BANK
		movb	LED_PORT.LED, /ledPort.LED
		jmp	:ledBlinkerEnd

:ledHere	mov	w, tcpTimerMSB
		and	w, #%00001111
		xor	w, #1
		jnz	:ledBlinkerEnd
		_bank	MISC_BANK
		movb	LED_PORT.LED, ledPort.LED
:ledBlinkerEnd

ISRExit		mov	w, #-INT_PERIOD
		retiw


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

Init		jmp	_Init
ARPInit		jmp	_ARPInit
TCPIPInit	jmp	_TCPIPInit
E2Init		jmp	_E2Init
StartupDelay	jmp	_StartupDelay

Main
		call	@Init

; DHCP Dynamic IP Address solicitation
	IF DHCP
		; initialize UDP receive port for DHCP
		_bank	UDP_BANK
		mov	udpRxDestPortMSB, #0	; DHCP(BOOTP) Client
		mov	udpRxDestPortLSB, #68	;

		; send DHCPDISCOVER message to find DHCP server(s)
		call	@DHCPDISCOVERSend

		; wait for DHCPOFFER
:dhcpWaitOffer	call	@NICWaitRxFrame
		call	@CheckIPDatagram
		jnb	flags.RX_IS_UDP, :dhcpWaitOffer
		call	@UDPProcPktIn
		sb	flags.GOT_DHCP_OFFER
		jmp	:dhcpWaitOffer

		; send DHCPREQUEST message
		call	@DHCPREQUESTSend

		; wait for DHCPACK
:dhcpGotOffer	call	@NICWaitRxFrame
		call	@CheckIPDatagram
		jnb	flags.RX_IS_UDP, :dhcpGotOffer
		call	@UDPProcPktIn
		sb	flags.GOT_IP_ADDR
		jmp	:dhcpGotOffer

:dhcpGotAck	; hallelujah!
	ENDIF

		call	@UDPAppInit

		_bank	MISC_BANK
		clr	pageCount		; clear page counter (dynamic data)
		mov	ledPort, LED_PORT

; main program loop
:mainLoop	call	@NICCheckRxFrame
		jz	:noRxFrame

		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

		call	@NICDumpRxFrame		; not something we recognize, so dump it
		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	TCPState, #TCP_ST_CLOSED, :tcpClosed
		cje	TCPState, #TCP_ST_LISTEN, :tcpListen

		bank	TIMER_BANK
		cjae	connTimerMSB, #TCP_CONN_EXP, :resetTCP
		sb	arpFlags.ARP_REQ_SENT	; do not allow new tx if waiting for ARP response
		call	@TCPTransmit		; check if app has anything to transmit
		jmp	:mainLoop

:tcpClosed	call	@TCPAppInit
		jmp	:mainLoop

:tcpListen	bank	TIMER_BANK
		clr	connTimerMSB
		jmp	:mainLoop

:resetTCP	; reset hung TCP connection
		bank	TCP_BANK
		clr	tcpUnAckMSB
		clr	tcpUnAckLSB
		mov	tcpState, #TCP_ST_CLOSED
		bank	HTTP_BANK
		clr	httpParseState
		jmp	:mainLoop


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

	ORG	$190	; 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

		call	@NICInit
		call	@ARPInit
		call	@TCPIPInit
		call	@E2Init

		mov	w, #(RTCC_PS_OFF)	; setup option register
		mov	!option, w

		retp

; ******************************************************************************
_ARPInit
; ARP initialization code
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		clr	arpFlags
		_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	tcpUnAckMSB
		clr	tcpUnAckLSB
		mov	tcpState, #TCP_ST_CLOSED

		retp

; ******************************************************************************
_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

; ******************************************************************************
_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


	ORG	$200	; Page1

NICSendTxFrame	jmp	_NICSendTxFrame
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

		call	@StartupDelay		; give it a little time to come out of POR
		mov	nicIOAddr, #$1F		; write to reset port
		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, #%11000100
		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
		jmp	NICWrite

; ******************************************************************************
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, #%00100010		; Page0, abort DMA
		call	NICWrite

		mov	nicIOAddr, #$07		; ISR
		call	NICRead
		;jb	wreg.4, :overflowed	; OVW (bit set when rx buffer overflowed) DEBUG
		jb	wreg.4, @Main		; OVW (bit set when rx buffer overflowed)-> reset

		clr	nicIOAddr		; CR
		mov	w, #%01000010		; Page1
		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

;:overflowed	mov	w, #(RTCC_ID)	; DEBUG
;		mov	!option, w	; DEBUG
;		clrb	LED_PIN		; DEBUG
;		jmp	$		; DEBUG

; ******************************************************************************
NICWaitRxFrame
; Wait for an ethernet frame to be received.
; INPUT:  none
; OUTPUT: nicCurrPktPtr = points to beginning of packet just received
;	  nicNextPktPtr = points to beginning of next packet
;	  nicRemoteEth0-5 = source (remote) ethernet address
; ******************************************************************************
		_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

		jz	:loop			; CURR = BNRY => no packets

		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
		jmp	NICWrite

; ******************************************************************************
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
		jmp	NICWrite

; ******************************************************************************
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>
		jmp	NICWriteSrcEth

; ******************************************************************************
_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
; ******************************************************************************
		; sometimes we need to bypass ARP (e.g. broadcast IP addr)
		jb	arpFlags.ARP_BYPASS, :here

		; otherwise we need to check if we know the MAC mapping for the IP addr
		call	@ARPCheckCache		; Start ARP
		snb	arpFlags.ARP_REQ_SENT	; Continue if an ARP request was not sent
						; or if a stalled packet is to be sent
		retp				; exit, ARP request was sent. packet to be stalled

:here		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
		jmp	NICWrite

; ******************************************************************************
_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
		jmp	NICWrite

; ******************************************************************************
_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
		jmp	NICRead

; ******************************************************************************
_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>
		jmp	NICWriteDestIP

; ******************************************************************************
_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
		bank	IP_BANK
		mov	w, myIP3
		call	NICWrite
		bank	IP_BANK
		mov	w, myIP2
		call	NICWriteAgain
		mov	w, myIP1
		call	NICWriteAgain
		mov	w, myIP0
		jmp	NICWriteAgain

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

; ******************************************************************************
_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
		jmp	NICWriteAgain

; ******************************************************************************
_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
		jmp	NICWriteAgain

; ******************************************************************************
_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
		jmp	NICWriteAgain

; ******************************************************************************
_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
		jmp	NICWrite


	ORG	$400	; Page2

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

; ******************************************************************************
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	arpFlags.ARP_STL_TX, :cacheGo	; do not check cache if stalled packet to be txed
		call	ARPUpdateEthAddr		; now update stalled packet's ethernet address
		clr	arpFlags			; 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	arpFlags.ARP_REQ_SENT	; indicate an ARP request is sent
		jmp	ARPSendRequest		; do an ARP request now to get the remote Eth address

:cacheMatch	clr	arpFlags		; reset ARP
		jmp	ARPUpdateEthAddr	; check, update remote Eth address of pending packet in NIC

; ******************************************************************************
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	; stPktTxBufStart contains page number of stalled pkt
		call	NICWrite_2

		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

:rcvdARPRequest
		; ignore <sender_HA>
		call	NICPseudoRead6_2

		; if a TCP connection has been established, check to see if remote
		; IP is the one we've established connection with, otherwise, make
		; sure {remoteIP3-0} isn't clobbered
		bank	TCP_BANK
		cjne	tcpState, #TCP_ST_ESTABED, :senderIP
		; check <source_IP>
		mov	fsr, #remoteIP3
		call	ARPCompare4
		jnz	:outtaHere
		jmp	:targetHA

		; record the sender's IP addr (<sender_IP>)
: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	arpFlags.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	arpFlags.ARP_RSP_RCVD	; indicate we got a successfull ARP response

: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, #TXBUF3_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, #TXBUF3_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	arpFlags.ARP_REQ_SENT	; check if we are waiting for an ARP response
		retp				; no, nothing to do here

		jnb	arpFlags.ARP_RSP_RCVD, :checkARPTimeout	; if no ARP response rcvd, check if timeout

		; yes we have rcvd a response so now we can send the stalled packet
		clr	arpFlags		; reset ARP
		setb	arpFlags.ARP_STL_TX	; indicate that stalled packet is to be transmitted

		; 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

		clr	arpFlags			; yes, reset ARP flags
		; Very important now! 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 match in the ARP cache since the original IP is still there!
		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
; ******************************************************************************
		mov	w, #~((1<<RX_IS_ICMP)|(1<<RX_IS_UDP)|(1<<RX_IS_TCP)|(1<<RX_IS_IP_BCST))
		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

		; if a TCP connection has been established, check to see if remote
		; IP is the one we've established connection with, otherwise, make
		; sure {remoteIP3-0} isn't clobbered
		bank	TCP_BANK
		cjne	tcpState, #TCP_ST_ESTABED, :srcIP
		; check <source_IP>
		bank	IP_BANK
		call	NICReadAgain_2
		xor	w, remoteIP3
		jnz	:outtaHere
		call	NICReadAgain_2
		xor	w, remoteIP2
		jnz	:outtaHere
		call	NICReadAgain_2
		xor	w, remoteIP1
		jnz	:outtaHere
		call	NICReadAgain_2
		xor	w, remoteIP0
		jnz	:outtaHere
		jmp	:destIP

		; 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


	ORG	$600	; Page3

TCPTransmit	jmp	_TCPTransmit
TCPReTransmit	jmp	_TCPReTransmit
TCPAppPassiveOpen jmp	_TCPAppPassiveOpen
TCPAppActiveOpen jmp	_TCPAppActiveOpen
TCPAppClose	jmp	_TCPAppClose

; ******************************************************************************
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

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

; ******************************************************************************
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

: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			; TCP protocol
		xor	w, ipProtocol
		sz
		mov	w, #TXBUF1_START	; default tx buffer is TXBUF1
		snz
		mov	w, #TXBUF2_START	; unless it's TCP, then we use TXBUF2
		bank	ARP_BANK
		sb	arpFlags.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

; ******************************************************************************
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 the special states
		bank	TCP_BANK
		cje	tcpState, #TCP_ST_CLOSED, :CLOSED
		cje	tcpState, #TCP_ST_LISTEN, :LISTEN
		cje	tcpState, #TCP_ST_SYNSENT, :SYNSENT
		cje	tcpState, #TCP_ST_FINWAIT1, :FINWAIT1
		cje	tcpState, #TCP_ST_FINWAIT2, :FINWAIT2
		cje	tcpState, #TCP_ST_LASTACK, :LASTACK

		; check the flags
		bank	TCB_BANK
		snb	tcbFlags.TCP_FLAG_RST		; is the reset flag set?
		jmp	:gotoClosed			; yes, drop packet and close the connection
		snb	tcbFlags.TCP_FLAG_SYN		; is the SYN bit set?
		jmp	@TCPSendReset			; yes, drop the packet and send a reset
		sb	tcbFlags.TCP_FLAG_ACK		; is the ACK bit set?
		jmp	NICDumpRxFrame_3		; no, drop the packet

		call	@TCPChkSeq			; check if received packet is expected

		; we only accept ACKs of complete packets. Assume the ACK is for our last packet

		bank	TCP_BANK
		mov	tcpState, #TCP_ST_ESTABED	; switch to the established state

: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 application

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

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

:doClose	call	@TCPIncRcvNxt			; ACK the FIN
		bank	TCP_BANK
		mov	tcpState, #TCP_ST_CLOSEWAIT	; change state
		jmp	@TCPSendAck

:gotoClosed	bank	TCP_BANK
		mov	tcpState, #TCP_ST_CLOSED	; go to the closed state
		jmp	NICDumpRxFrame_3		; discard received packet

:FINWAIT1	call	NICDumpRxFrame_3
		bank	TCP_BANK
		sb	tcpRxFlags.TCP_FLAG_ACK		; check for ACK of FIN
		retp
		mov	tcpState, #TCP_ST_FINWAIT2	; rcved ACK of FIN
		retp

:FINWAIT2	call	NICDumpRxFrame_3
		bank	TCP_BANK
		sb	tcpRxFlags.TCP_FLAG_FIN		; check for FIN
		retp
		mov	tcpState, #TCP_ST_CLOSED	; rcved FIN
		call	@TCPIncRcvNxt			; ACK the FIN
		jmp	@TCPSendAck

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

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

:LISTEN		call	NICDumpRxFrame_3		; discard received packet
		bank	TCB_BANK
		snb	tcbFlags.TCP_FLAG_RST		; check for an RST
		retp					; ignore a packet with RST
		snb	tcbFlags.TCP_FLAG_ACK		; check for an ACK
		jmp	@TCPSendReset			; bad ACK, send a RST
		call	@TCPCopySeqToNxt
		call	@TCPSendSynAck
		bank	TCP_BANK
		mov	tcpState, #TCP_ST_SYNRCVED	; change state
		retp

:SYNSENT	call	NICDumpRxFrame_3
		bank	TCB_BANK
		jb	tcbFlags.TCP_FLAG_RST, :rst	; is the reset bit set?
		sb	tcbFlags.TCP_FLAG_SYN		; if SYN bit not set,
		retp					; ignore packet
		sb	tcbFlags.TCP_FLAG_ACK		; if ACK bit not set,
		retp					; ignore packet

		; check that SND.UNA <= SEG.ACK <= SND.NXT
		bank	TCP_BANK
		mov	tcpState, #TCP_ST_ESTABED	; the connection is now estabished
		bank	IP_BANK
		clr	ipLengthMSB			; set the received data length to one
		mov	ipLengthLSB, #1			;  "
		call	@TCPAckUpdate
		jmp	@TCPSendAck

:rst		bank	TCP_BANK
		mov	tcpState, #TCP_ST_CLOSED	; close the TCP
		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
		cje	tcpState, #TCP_ST_SYNSENT, :timeout	; check for SYN timeout
		cje	tcpState, #TCP_ST_CLOSEWAIT, :closeWait
		cjae	tcpState, #TCP_ST_ESTABED, :ok
		retp
:ok		test	tcpUnAckMSB		; are there any bytes unacknowledged?
		jnz	:timeout
		test	tcpUnAckLSB
		jnz	:timeout
		cje	tcpState, #TCP_ST_FINWAIT1, :finTimeout	; check for FIN timeout

:askAppTxData	cse	tcpState, #TCP_ST_ESTABED
		retp				; only ask if connection is established
		call	@TCPAppTxBytes		; no, ask the application if it wants to transmit
		_bank	TCP_BANK		; _bank cus we called TCPAppTxBytes priorly
		mov	w, tcpUnAckMSB
		or	w, tcpUnAckLSB
		jnz	:appHasTxData
		retp				; nope, it doesn't; so we're done here then

:appHasTxData	; start a TCP packet
		mov	tcpLengthLSB, tcpUnAckLSB	; tcpLength = # bytes to transmit
		mov	tcpLengthMSB, tcpUnAckMSB
		bank	TCB_BANK
		mov	tcbFlags, #((1<<TCP_FLAG_PSH)|(1<<TCP_FLAG_ACK))
		call	@TCPCheckSumInit
		call	@TCPStartPktOut		; send the header

		call	@TCPUpdateSeq		; Update the outgoing sequence no

		; insert the packet data while computing the checksum over the data
		bank	TCP_BANK
		mov	tcpTmpLSB, tcpUnAckLSB
		mov	tcpTmpMSB, tcpUnAckMSB
		inc	tcpTmpMSB		; so that we can loop easier later
:dataLoop	call	@TCPAppTxData
		_banky	TCP_BANK		; cus we called TCPAppTxData priorly
		call	NICWriteAgain_3		; 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
		mov	nicCopySrcMSB, #TXBUF2_START
		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
		clr	tcpTimerMSB
		clr	tcpTimerLSB
		retp

:finTimeout	bank	TIMER_BANK
		csae	tcpTimerMSB, #TCP_RESTART_EXP	; has the restart timer expired?
		retp					; no
		clr	tcpTimerMSB			; yes, initialise the restart timer
		clr	tcpTimerLSB
		jmp	@TCPSendFin

:timeout	bank	TIMER_BANK
		csae	tcpTimerMSB, #TCP_RESTART_EXP	; has the restart timer expired?
		retp					; no
		clr	tcpTimerMSB			; yes, initialise the restart timer
		clr	tcpTimerLSB
		jmp	@TCPReTransmit			; transmit the packet again

:closeWait	mov	tcpState, #TCP_ST_LASTACK
		jmp	@TCPSendFin			; send FIN, ACK

; ******************************************************************************
_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_3
		mov	nicIOAddr, #$04		; TPSR
		mov	w, #TXBUF2_START
		call	NICWrite_3

		; read the NIC's TCP transmit buffer to find out the IP <length>
		; so that we can re-initialize the NIC's TBCR
		mov	nicCopySrcMSB, #TXBUF2_START
		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

; ******************************************************************************
_TCPAppPassiveOpen
; Do a passive open. i.e. listen for connections on a given port.
; [TCP API Function]
; INPUT:  {tcbLocalPortMSB,tcbLocalPortLSB} = TCP port to listen on
; OUTPUT: none
; ******************************************************************************
		_bank	TCP_BANK
		mov	tcpState, #TCP_ST_LISTEN
		clr	tcpUnAckMSB
		clr	tcpUnAckLSB
		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
;	  {tcpRemotePortMSB,tcbRemotePortLSB} = remote TCP port
; OUTPUT: none
; ******************************************************************************
		_bank	TCP_BANK
		clr	tcpUnAckMSB
		clr	tcpUnAckLSB
		mov	tcpState, #TCP_ST_SYNSENT
		bank	TCB_BANK
		mov	tcbFlags, #(1<<TCP_FLAG_SYN)
		jmp	@TCPSendSyn

; ******************************************************************************
_TCPAppClose
; Force the current connection to close
; [TCP API Function]
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		_bank	TCP_BANK
		mov	tcpState, #TCP_ST_FINWAIT1
		clr	tcpUnAckMSB
		clr	tcpUnAckLSB
		bank	TCB_BANK
		mov	tcbFlags, #((1<<TCP_FLAG_FIN)|(1<<TCP_FLAG_ACK))
		jmp	@TCPSendEmptyPkt


	ORG	$800	; Page4

TCPRxHeader	jmp	_TCPRxHeader

TCPChkSeq	jmp	_TCPChkSeq
TCPRestorePrev	jmp	_TCPRestorePrev
TCPUpdateSeq	jmp	_TCPUpdateSeq

; ******************************************************************************
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: {tcbRcvNxt1-4}
; ******************************************************************************
		bank	IP_BANK
		mov	w, ipLengthLSB
		bank	TCB_BANK
		add	tcbRcvNxt1, w
		bank	IP_BANK
		mov	w, ipLengthMSB
		snc
		mov	w, ++ipLengthMSB
		bank	TCB_BANK
		add	tcbRcvNxt2, w
		sc
		retp
		incsz	tcbRcvNxt3
		retp
		inc	tcbRcvNxt4
		retp

; ******************************************************************************
TCPIncRcvNxt
; Increment RCV.NXT by one
; INPUT:  none
; OUTPUT: {tcbRcvNxt1-4}
; ******************************************************************************
		bank	TCB_BANK
		inc	tcbRcvNxt1
		sz
		retp
		incsz	tcbRcvNxt2
		retp
		incsz	tcbRcvNxt3
		retp
		inc	tcbRcvNxt4
		retp

; ******************************************************************************
TCPIncSndUna
; Increment SND.UNA by one
; INPUT:  none
; OUTPUT: {tcbSndUna1-4}
; ******************************************************************************
		bank	TCB_BANK
		inc	tcbSndUna1
		sz
		retp
		incsz	tcbSndUna2
		retp
		incsz	tcbSndUna3
		retp
		inc	tcbSndUna4
		retp

; ******************************************************************************
TCPCopySeqToNxt
; Copy {tcpTmpSeq4-1} -> {tcbRcvNxt4-1}
; INPUT:  {tcpTmpSeq4-1}
; OUTPUT: {tcbRcvNxt4-1}
; ******************************************************************************
		bank	TCP_BANK
		mov	w, tcpTmpSeq4
		bank	TCB_BANK
		mov	tcbRcvNxt4, w
		bank	TCP_BANK
		mov	w, tcpTmpSeq3
		bank	TCB_BANK
		mov	tcbRcvNxt3, w
		bank	TCP_BANK
		mov	w, tcpTmpSeq2
		bank	TCB_BANK
		mov	tcbRcvNxt2, w
		bank	TCP_BANK
		mov	w, tcpTmpSeq1
		bank	TCB_BANK
		mov	tcbRcvNxt1, w
		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	@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
;	  {tcbRcvNxt4-1} = RCV.NXT
; OUTPUT: z is set if RCV.NXT == SEG.SEQ
; ******************************************************************************
		bank	TCB_BANK
		mov	w, tcbRcvNxt1
		bank	TCP_BANK
		xor	w, tcpTmpSeq1
		sz
		retp
		bank	TCB_BANK
		mov	w, tcbRcvNxt2
		bank	TCP_BANK
		xor	w, tcpTmpSeq2
		sz
		retp
		bank	TCB_BANK
		mov	w, tcbRcvNxt3
		bank	TCP_BANK
		xor	w, tcpTmpSeq3
		sz
		retp
		bank	TCB_BANK
		mov	w, tcbRcvNxt4
		bank	TCP_BANK
		xor	w, tcpTmpSeq4
		retp

; ******************************************************************************
TCPSendEmptyPkt
; Constructs and sends a TCP packet containing no data
; INPUT:  {remoteIP0-3} = destination IP addr for TCP pkt
;	  {tcbSndUna4-1} = sequence number
;	  {tcbRcvNxt4-1} = acknowledgement number
;	  tcbFlags = code flags
;	  {tcbLocalPortMSB,tcbLocalPortLSB} = TCP Source Port
;	  {tcbRemotePortMSB,tcbRemotePortLSB} = TCP Destination Port
; OUTPUT:
; ******************************************************************************
		call	@TCPCheckSumInit
		bank	TCP_BANK
		clr	tcpLengthMSB
		clr	tcpLengthLSB
		call	@TCPStartPktOut
		call	@NICSendTxFrame
		bank	TIMER_BANK
		clr	tcpTimerMSB
		clr	tcpTimerLSB
		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
;	  {tcbRcvNxt4-1} = acknowledgement number
;	  {tcbLocalPortMSB,tcbLocalPortLSB} = TCP Source Port
;	  {tcbRemotePortMSB,tcbRemotePortLSB} = TCP Destination Port
; OUTPUT:
; ******************************************************************************
		bank	TCB_BANK
		mov	tcbFlags, #(1<<TCP_FLAG_RST)
		call	@TCPSendEmptyPkt
		jmp	NICDumpRxFrame_4	; discard the received pkt

; ******************************************************************************
TCPSendSyn
; Send a SYN packet
; INPUT:  {remoteIP0-3} = destination IP addr for TCP pkt
;	  {tcbRcvNxt4-1} = acknowledgement number
;	  {tcbLocalPortMSB,tcbLocalPortLSB} = TCP Source Port
;	  {tcbRemotePortMSB,tcbRemotePortLSB} = TCP Destination Port
; OUTPUT: {tcpSndUna4-1} = new sequence number
; ******************************************************************************
		bank	TCB_BANK
		mov	tcbFlags, #(1<<TCP_FLAG_SYN)
		jmp	TCPSendISN

; ******************************************************************************
TCPSendISN
; Send the TCP initial sequence number
; INPUT:  {remoteIP0-3} = destination IP addr for TCP pkt
;	  {tcbRcvNxt4-1} = acknowledgement number
;	  {tcbLocalPortMSB,tcbLocalPortLSB} = TCP Source Port
;	  {tcbRemotePortMSB,tcbRemotePortLSB} = 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
		bank	TCB_BANK
		mov	tcbSndUna4, w
		mov	tcbSndUna3, w
		mov	tcbSndUna2, w
		mov	tcbSndUna1, w

		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
;	  {tcbRcvNxt4-1} = acknowledgement number
;	  {tcbLocalPortMSB,tcbLocalPortLSB} = TCP Source Port
;	  {tcbRemotePortMSB,tcbRemotePortLSB} = TCP Destination Port
; OUTPUT: {tcpSndUna4-1} = new sequence number
; ******************************************************************************
		bank	TCB_BANK
		mov	tcbFlags, #((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
;	  {tcbSndUna4-1} = sequence number
;	  {tcbRcvNxt4-1} = acknowledgement number
;	  {tcbLocalPortMSB,tcbLocalPortLSB} = TCP Source Port
;	  {tcbRemotePortMSB,tcbRemotePortLSB} = TCP Destination Port
; OUTPUT:
; ******************************************************************************
		bank	TCB_BANK
		mov	tcbFlags, #(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
;	  {tcbSndUna4-1} = sequence number
;	  {tcbRcvNxt4-1} = acknowledgement number
;	  tcbFlags = code flags
;	  {tcbLocalPortMSB,tcbLocalPortLSB} = TCP Source Port
;	  {tcbRemotePortMSB,tcbRemotePortLSB} = TCP Destination Port
; OUTPUT:
; ******************************************************************************
		bank	TCB_BANK
		mov	tcbFlags, #(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
;	  {tcbSndUna4-1} = sequence number
;	  {tcbRcvNxt4-1} = acknowledgement number
;	  tcbFlags = code flags
;	  {tcbLocalPortMSB,tcbLocalPortLSB} = TCP Source Port
;	  {tcbRemotePortMSB,tcbRemotePortLSB} = 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

		; compute IP <header_checksum>
		call	@IPGenCheckSum

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

		; then construct the TCP header

		; TCP <source_port>,<destination_port>,<sequence_number>,
		; <acknowledgement_number>,<hlen>,<code>,<window>
		bank	TCB_BANK
		mov	tcbOffset, #(TCP_HDR_LENGTH<<4)
		mov	tcbSendWinMSB, #((TCP_WINDOW_SIZE&$FF00)>>8)
		mov	tcbSendWinLSB, #(TCP_WINDOW_SIZE&$00FF)

		mov	globTemp3, #TCB_BANK	; send the TCB fields
:loop		mov	fsr, globTemp3
		mov	w, indf			; load the value
		call	@TCPTxByte		; transmit and accumulate header checksum
		inc	globTemp3
		cse	globTemp3, #TCB_END	; is the loop finished?
		jmp	:loop

		; 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
;	  {tcbRemotePortMSB,tcbRemotePortLSB}
;	  {tcbSendWinMSB,tcbSendWinLSB}
;	  tcbOffset
;	  tcbFlags
;	  tcpRxFlags
;	  {tcpTmpSeq4-1} = <sequence_number>
;	  {tcpTmpAck4-1} = <acknowledgement_number>
;	  {tcpLengthMSB,tcpLengthLSB} = length of TCP data
;	  {ipLengthMSB,ipLengthLSB} = length of TCP data
; ******************************************************************************
		; Check port and refuse packet if not for current connection
		bank	TCP_BANK
		cjne	tcpState, #TCP_ST_LISTEN, :checkSrc

		; <source_port>
:readSrc	; read the source port
		bank	TCB_BANK
		call	NICReadAgain_4
		mov	tcbRemotePortMSB, w
		call	NICReadAgain_4
		mov	tcbRemotePortLSB, w
		jmp	:checkDest

:checkSrc	; check the source port is for the current connection
		bank	TCB_BANK
		call	NICReadAgain_4
		xor	w, tcbRemotePortMSB
		sz				; is the high byte the same?
		jmp	:ignorePacket		; no, ignore the packet
		call	NICReadAgain_4		; get the low byte
		xor	w, tcbRemotePortLSB
		sz				; is the low byte the same?
		jmp	:ignorePacket		; no, ignore the packet

		; <destination_port>
:checkDest	; check the destination port matches our port
		call	NICReadAgain_4
		xor	w, tcbLocalPortMSB
		sz				; is the high byte the same?
		jmp	:ignorePacket		; no, ignore the packet
		call	NICReadAgain_4		; get the low byte
		xor	w, tcbLocalPortLSB
		sz				; Is the low byte the same?
		jmp	:ignorePacket		; no, ignore the packet

		; <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

		; <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

		; <hlen>
		call	NICReadAgain_4		; receive the data offset. Used to skip the options
		and	w, #TCP_OFFSET_MASK	; mask out the offset
		bank	TCB_BANK
		mov	tcbOffset, w
		clc
		rr	tcbOffset		; shift right to get the number of bytes
		rr	tcbOffset

		; ipLength = tcpLength = length of TCP data
		mov	w, tcbOffset		; size of TCP header in bytes
		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
		bank	TCB_BANK
		mov	tcbFlags, w		; take a copy of the flags

		; <window>
		call	NICReadAgain_4
		mov	tcbSendWinMSB, w	; receive the window
		call	NICReadAgain_4
		mov	tcbSendWinLSB, w

		; <checksum>,<urgent_ptr>,<options>
		; skip over these
		bank	TCB_BANK
		sub	tcbOffset, #((TCP_HDR_LENGTH<<2)-4)
:loop		call	NICReadAgain_4
		decsz	tcbOffset
		jmp	:loop
		clz
		retp

:ignorePacket	; ignore the rest of the packet.
		call	NICDumpRxFrame_4
		; ## Need to send a reset in response to this packet.
		; ## Unfortunately sending a packet would overwrite the TCB.
		; ## Need to create a second TCB?
		stz
		retp


; ******************************************************************************
_TCPUpdateSeq
; Add an 16-bit number to outgoing Sequence nr
; INPUT:  {tcbSndUna1-4}{tcpUnAckMSB/LSB} = number to add
; OUTPUT: {tcbSndUna1-4}
; ******************************************************************************

		bank	TCP_BANK
		mov	w, tcpUnAckLSB
		bank	TCB_BANK
		add	tcbSndUna1, w
		bank	TCP_BANK
		mov	w, tcpUnAckMSB
		snc
		mov	w, ++tcpUnAckMSB
		bank	TCB_BANK
		add	tcbSndUna2, w
		sc
		retp
		incsz	tcbSndUna3
		retp
		inc	tcbSndUna4
		retp


; ******************************************************************************
_TCPChkSeq
; This is called in the TCPProcPktIn to check if the received packet is the one
; expected or if it is a previous unacked packet.
; [TCP API Function]
; INPUT:  none
; OUTPUT: z flag is set if the received packet is incorrect
; ******************************************************************************

		call	TCPCmpNxtSeq	; Check if received is expected
		jz	:equal		; z is set if RCV.NXT == SEG.SEQ

		_bank	TCP_BANK
		test	tcpUnAckLSB
		sz
		jmp	:outstanding
		test	tcpUnAckMSB
		snz
		jmp	:noOutstanding

:outstanding	call	TCPRestorePrev	; RCV.NXT = RCV.NXT - tcpUnAck
		call	TCPCmpNxtSeq	; Check if received is ack on previous packet
		jnz	:noOutstanding	; z is set if RCV.NXT == SEG.SEQ
		
		setb	z		; Return value = "OK"
		retp

:equal		_bank	TCP_BANK
		clr	tcpUnAckLSB
		clr	tcpUnAckMSB	; Z is set (return value = "OK")
		retp

:noOutstanding	clrb	LED_PIN		; DEBUG TODO
		jmp	$		; DEBUG


; ******************************************************************************
_TCPRestorePrev
; Subtract an 16-bit number from RCV.NXT
; INPUT:  {tcpUnAckLSB,tcpUnAckMSB} = number to add
; OUTPUT: {tcbRcvNxt1-4}
; ******************************************************************************
		bank	TCP_BANK
		mov	w, tcpUnAckLSB
		bank	TCB_BANK
		sub	tcbRcvNxt1, w
		bank	TCP_BANK
		mov	w, tcpUnAckMSB
		jc	:dontBorrow		

		mov	w, ++tcpUnAckMSB	; if prev.sub was negative
		test	w			; carry on inc?
		jz	:exit			; yes (w = $100)
:dontBorrow
		bank	TCB_BANK
		sub	tcbRcvNxt2, w
		snc				; c=0 => negative result
		retp

:exit		bank	TCB_BANK
		test	tcbRcvNxt3
		snz
		dec	tcbRcvNxt4
		dec	tcbRcvNxt3
		retp


	ORG	$A00	; Page5

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

; ******************************************************************************
TCPAppInit
; Called repeatedly as long as TCP connection state is closed
; [TCP API Function]
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		bank	TCB_BANK
		mov	tcbLocalPortLSB, #HTTP_PORT_LSB
		mov	tcbLocalPortMSB, #HTTP_PORT_MSB
		bank	HTTP_BANK
		clr	httpParseState
		clr	httpURIHash
		jmp	@TCPAppPassiveOpen

; ******************************************************************************
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: {tcpUnAckMSB,tcpUnAckLSB} = number of bytes to transmit
; ******************************************************************************
		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	tcpUnAckMSB, #(HTTP_SEG_SIZE>>8)
		mov	tcpUnAckLSB, #(HTTP_SEG_SIZE&$00FF)
		retp

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

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

:state4		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
; ******************************************************************************
		bank	HTTP_BANK
		cje	httpURIHash, #URI1, :specialFile	; resource.htm
		cje	httpURIHash, #URI2, :specialFile	; temperature.htm
		call	@E2Read8Ack
		retp

:specialFile	call	@E2Read8Ack
		mov	globTemp1, w			; save temporarily
		csb	globTemp1, #$F0
		jmp	:match
		mov	w, globTemp1
		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
		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
; ******************************************************************************
		retp

; ******************************************************************************
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
; ******************************************************************************
		retp

; ******************************************************************************
TCPAppRxData
; Called once for each byte received in a packet.
; [TCP API Function]
; INPUT:  w = received data byte
; OUTPUT: none
; ******************************************************************************
		mov	globTemp1, w
		bank	HTTP_BANK
		test	httpParseState
		jz	:state0
		cje	httpParseState, #1, :state1
		cjae	httpParseState, #2, :state2

:state0		cse	globTemp1, #' '
		retp
		inc	httpParseState	; next-state = 1
		retp

:state1		cje	globTemp1, #' ', :gotURI
		add	httpURIHash, globTemp1
		retp

:gotURI		; obtain pointer to file
		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

		inc	httpParseState	; next-state = 2
		retp

:state2		retp

; ******************************************************************************
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
; ******************************************************************************
		retp

; ******************************************************************************
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
; ******************************************************************************
		call	E2SDAOutputLo
		call	E2Delay900ns
		setb	E2SCL_PIN
		call	E2Delay600ns
		clrb	E2SCL_PIN
		call	E2Delay900ns
		retp

; ******************************************************************************
_E2SendNotAck
; Send ACK bit as not-acknowledge
; INPUT:  none
; ******************************************************************************
		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


	ORG	$C00	; Page6

	IF	DHCP
DHCPREQUESTSend	jmp	_DHCPREQUESTSend
	ENDIF

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

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

; ******************************************************************************
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_6
		mov	w, udpTxSrcPortLSB
		call	NICWriteAgain_6

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

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

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

		retp

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

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

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

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

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

		; ignore UDP <checksum>
	REPT	2
		call	NICReadAgain_6
	ENDR

		; UDP <data>
		snb	flags.RX_IS_IP_BCST
		call	UDPProcBcstPktIn
		sb	flags.RX_IS_IP_BCST
		call	@UDPAppProcPktIn

:outtaHere	call	@NICDumpRxFrame
		retp

	IF	DHCP

; ******************************************************************************
UDPProcBcstPktIn
; The only kind of broadcast UDP packets accepted are DHCP messages: DHCPOFFER
; and DHCPACK
; INPUT:  none
; OUTPUT: myIP0-3
; ******************************************************************************
		clrb	flags.GOT_DHCP_OFFER
		clrb	flags.GOT_IP_ADDR

		call	NICReadAgain_6
		xor	w, #2			; check <op> = BOOTP reply
		jnz	:outtaHere
		call	NICReadAgain_6
		xor	w, #1			; check <htype> = 1
		jnz	:outtaHere
		call	NICReadAgain_6
		xor	w, #6			; check <hlen> = 6
		jnz	:outtaHere
		; ignore <hops>
		call	NICReadAgain_6
		; check <transaction_id> = 0xABABABAB
	REPT	4
		call	NICReadAgain_6
		xor	w, #$AB
		jnz	:outtaHere
	ENDR
		; ignore <seconds>, <flags>, <client_IP>
		mov	globTemp1, #(2+2+4)
:loop1		call	NICReadAgain_6
		decsz	globTemp1
		jmp	:loop1
		; record <your_IP>
		bank	IP_BANK
		call	NICReadAgain_6
		mov	myIP3, w
		call	NICReadAgain_6
		mov	myIP2, w
		call	NICReadAgain_6
		mov	myIP1, w
		call	NICReadAgain_6
		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_6
		xor	w, #53			; DHCP Message Type
		jnz	:outtaHere
		call	NICReadAgain_6
		xor	w, #1
		jnz	:outtaHere
		call	NICReadAgain_6
		xor	w, #2			; DHCPOFFER
		snz
		setb	flags.GOT_DHCP_OFFER
		xor	w, #2
		xor	w, #5			; DHCPACK
		snz
		setb	flags.GOT_IP_ADDR

		; now search for that dang(!) <option-server_id>
:loop4		call	NICReadAgain_6
		xor	w, #54			; Server Identifier
		jz	:foundServId
		call	NICReadAgain_6		; length
		mov	globTemp1, w
:loop3		call	@NICPseudoRead
		decsz	globTemp1
		jmp	:loop3
		jmp	:loop4

:foundServId	call	NICReadAgain_6		; ignore length
		bank	DHCP_BANK
		call	NICReadAgain_6
		mov	dhcpServerId3, w
		call	NICReadAgain_6
		mov	dhcpServerId2, w
		call	NICReadAgain_6
		mov	dhcpServerId1, w
		call	NICReadAgain_6
		mov	dhcpServerId0, w

:outtaHere	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

		; tell ARP not to send out an ARP REQUEST for this pkt
		setb	arpFlags.ARP_BYPASS

		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_6
		; <htype>
		mov	w, #1
		call	NICWriteAgain_6
		; <hlen>
		mov	w, #6
		call	NICWriteAgain_6
		; <hops>
		mov	w, #0
		call	NICWriteAgain_6
		; <transaction_id> = 0xABABABAB
		mov	w, #$AB
	REPT	4
		call	NICWriteAgain_6
	ENDR
		; <seconds> = 256
		mov	w, #1
	REPT	2
		call	NICWriteAgain_6
	ENDR
		; <flags>
		mov	w, #$80
		call	NICWriteAgain_6
		mov	w, #0
		call	NICWriteAgain_6

		retp

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

		retp

; ******************************************************************************
DHCPDISCOVERSend
; Send DHCPDISCOVER message
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		bank	UDP_BANK
		clr	udpTxDataLenMSB
		mov	udpTxDataLenLSB, #(240+3+0+1) ; without requested-IP option
		;mov	udpTxDataLenLSB, #(240+3+6+1) ; with requested-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_6
		decsz	globTemp1
		jmp	:loop1

		call	DHCPSendCommon2

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

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

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

		; and ... that should do it!
		call	@UDPEndPktOut

		clrb	arpFlags.ARP_BYPASS

		retp

; ******************************************************************************
_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_6
		mov	w, myIP2
		call	NICWriteAgain_6
		mov	w, myIP1
		call	NICWriteAgain_6
		mov	w, myIP0
		call	NICWriteAgain_6

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

		call	DHCPSendCommon2

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

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

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

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

		; and ... that should do it!

		call	@UDPEndPktOut

		clrb	arpFlags.ARP_BYPASS

		retp

	ELSE	; DHCP

; ******************************************************************************
UDPProcBcstPktIn
; The only kind of broadcast UDP packets accepted are DHCP messages: DHCPOFFER
; and DHCPACK
; INPUT:  none
; OUTPUT: none
; ******************************************************************************
		retp	; cus DHCP not enabled

	ENDIF	; DHCP


	ORG	$E00	; Page7 (Holly Cow! Still not done??)

; ******************************************************************************
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
	IF CREDENCE
		xor	re, w		; toggle I/O pins
		_bank	MISC_BANK
		mov	ledPort, re
	ELSE
		and	w, #%01000000
		xor	ra, w		; toggle I/O pins
		_bank	MISC_BANK
		mov	ledPort, ra
	ENDIF

		_bank	UDP_BANK
		clr	udpTxSrcPortMSB
		clr	udpTxSrcPortLSB

		mov	udpTxDestPortMSB, udpRxSrcPortMSB
		mov	udpTxDestPortLSB, udpRxSrcPortLSB

		mov	udpTxDataLenMSB, #0
		mov	udpTxDataLenLSB, #2

		call	@UDPStartPktOut

	IF CREDENCE
		mov	w, re		; send new port state
	ELSE
		mov	w, ra		; send new port state
	ENDIF
		call	@NICWriteAgain
		mov	w, #$00		; one-byte padding
		call	@NICWriteAgain

		call	@UDPEndPktOut

		retp


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

