please dont rip this site

Scenix Lib IO OSI3 Tcpip ISX_1_6_8.SRC

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



file: /Techref/scenix/lib/io/osi3/tcpip/isx_1_6_8.src, 128KB, , updated: 2002/2/21 12:32, local time: 2024/4/16 10:38,
TOP NEW HELP FIND: 
18.188.175.182:LOG IN

 ©2024 These pages are served without commercial sponsorship. (No popup ads, etc...).Bandwidth abuse increases hosting cost forcing sponsorship or shutdown. This server aggressively defends against automated copying for any reason including offline viewing, duplication, etc... Please respect this requirement and DO NOT RIP THIS SITE. Questions?
Please DO link to this page! Digg it! / MAKE!

<A HREF="http://massmind.org/Techref/scenix/lib/io/osi3/tcpip/isx_1_6_8.src"> scenix lib io osi3 tcpip isx_1_6_8</A>

Did you find what you needed?

 

Welcome to massmind.org!

 

Welcome to massmind.org!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  .