.include "C:\avr\inc\4433def.inc"

; **********************
; * Register Definitions
; **********************

.def Temp = r16
.def Temp2 = r17
.def Temp3 = r18
.def Net_Flags = r19

.def Fault = r20

.def lpm_reg = r0
.def lpm1 = r1
.def lpm2 = r2

.def Delay = r1
.def Delay2 = r2

.def SN1 = r1
.def SN2 = r2

.def CRC1 = r1
.def CRC2 = r2

; note that there are four names for the same registers above
; hence you have to use care when expecting a value to be there.
; in general it's only safe to assume the serial number, or whatever, is there
; when you first enter the function

; there's a implied bug here, though fortunately it hasn't come up yet
; because a interrupt can call get_serial at the same time the main program calls get_crc
; or something equally tacky.

.def SREGSwap = r3
.def One = r4
.def Null = r5

; the below registers are used for the 16x16 multiplication routines

.def	mc16uL	=r8		;multiplicand low byte
.def	mc16uH	=r9		;multiplicand high byte
.def	mp16uL	=r10		;multiplier low byte
.def	mp16uH	=r11		;multiplier high byte
.def	m16u0	=r10		;result byte 0 (LSB)
.def	m16u1	=r11		;result byte 1
.def	m16u2	=r12		;result byte 2
.def	m16u3	=r13		;result byte 3 (MSB)
.def	mcnt16u	=r21		;loop counter must take ldi-able

.def debug = r22

; ***********************
; * Net flags & constants
; ***********************

.equ NETBIT_REQCTS = 0
.equ NETBIT_GOTCTS = 1
.equ NETBIT_REQTX = 2
.equ NETBIT_BCAST = 4

;.equ NET_PACKET_LEN = 25
.equ NET_PACKET_LEN = 8
;.equ NET_CLKPRELOAD = 168
.equ NET_CLKPRELOAD = 100

; **************************
; * Net packet offsets
; **************************
.equ NETOFFSET_SRCADDR = 0
.equ NETOFFSET_DESTADDR = 2
.equ NETOFFSET_SRCDT = 4
.equ NETOFFSET_DESTDT = 5
.equ NETOFFSET_CMD = 6
;.equ NETOFFSET_PAYLOAD = 7
;.equ NETOFFSET_CRC = 23
.equ NETOFFSET_CRC = 7

; **************************
; * Net commands
; **************************
.equ NETCMD_NOOP = 0
.equ NETCMD_PING = 1
.equ NETCMD_PINGACK = 2
.equ NET_MAXCMD = NETCMD_PINGACK


; **************************
; * Net addr constants
; **************************
.equ NETADDR_BCASTLO = 255
.equ NETADDR_BCASTHI = 255
.equ NETADDR_UNDEFLO = 0
.equ NETADDR_UNDEFHI = 0

; **************************
; * Program Memory constants
; **************************

.equ PM_ADDR_CRC = $00e
.equ PM_ADDR_SERIAL = $00f
.equ PM_ADDR_STARTHOLE = $00e
.equ PM_ADDR_ENDHOLE = $00f
.equ PM_ADDR_ENDMEM = $800

; ***************
; * Addresses
; ***************

.equ ADDR_INCOMINGBASE = $60
.equ ADDR_OUTGOINGBASE = $7A ; 60 + 26
; we're now at $95 for further stuff

.equ ADDR_NETSTATE = $AD
.equ ADDR_COLLISSIONCNT = $AF
.equ ADDR_TXPTR = $B0
.equ ADDR_RXPTR = $B1
.equ ADDR_RNDSEED_LO = $B2
.equ ADDR_RNDSEED_HI = $B3
.equ ADDR_TEMP_SWAP = $B4
.equ ADDR_TEMP2_SWAP = $B5
.equ ADDR_TEMP3_SWAP = $B6
.equ ADDR_ZL_SWAP = $B7
.equ ADDR_ZH_SWAP = $B8
.equ ADDR_NETTICKCTR = $B9

.equ ADDR_LOERASE = $60
.equ ADDR_HIERASE = $BF

; ****************
; * network states
; ****************

.equ NETSTATE_IDLE = 0x00
.equ NETSTATE_TX = 0x01
.equ NETSTATE_TXCOL = 0x02
.equ NETSTATE_RX = 0x03
.equ NETSTATE_RXWAIT = 0x04
.equ NETSTATE_RXERROR = 0x05
.equ NETSTATE_TXWAIT = 0x06

.equ NETSTATE_MAX = NETSTATE_TXWAIT

; ********************
; * fault constants
; ********************

.equ FAULT_NETWORKSTATEOVERFLOW_RX = 0x01
.equ FAULT_NETWORKSTATEOVERFLOW_UDRE = 0x02
.equ FAULT_NETWORKSTATEOVERFLOW_TIM0 = 0x03
.equ FAULT_URXC_OVERFLOW = 0x04
.equ FAULT_RXWHILETX_UNDERRUN = 0x05
.equ FAULT_UNPROCESSEDRX = 0x06
.equ FAULT_TX_TIMEOUT = 0x07

.equ FAULT_UNEXPECTED_UDRE = 0x30 ; note unuusal inc
.equ FAULT_UNKNOWN_NETCMD = 0x20
.equ FAULT_UNEXPECTED_TIM0 = 0x10 ; note unusual inc




; **************************
; * Debugging faults
; **************************
.equ FAULT_COLLISION = 0x36
.equ FAULT_GOTCTS = 0x37
.equ FAULT_BLANKING = 0x38
.equ FAULT_RXDATA = 0x39
.equ FAULT_DONE = 0x3B
.equ FAULT_NOTTOUS = 0x3C

.equ FAULT_NOFAULT = 0x00
.equ FAULT_FRAMING_ERROR = 42

; ***********************
; * irq vector jump table
; ***********************

.org 0
rjmp init
.org OVF0addr
rjmp ovf0_int
.org URXCaddr
rjmp urxc_int
.org UDREaddr
rjmp udre_int
.org UTXCaddr
rjmp utxc_int
.org PM_ADDR_CRC
.dw 0x4444 ; CRC
.org PM_ADDR_SERIAL
.dw 0x0004; serial number

;************************************
;* INIT - bootstrap interrupt
;************************************

init:

ldi Temp, $DF
out SP, Temp ; setup stack pointer
rcall do_mem_init
; todo: POST
rcall do_io_init
; build a crc and output it
rcall build_crc

sei
rcall go_idle

;ldi fault, FAULT_NOFAULT ;debug
;rcall fault ;debug


die:
rcall svc_net
rjmp die


;************************************
;* URXC_INT - uart received char int
;************************************

urxc_int: ; receive char
;inc debug ; debug
;out PORTC, debug ; debug

;sbi PORTD, 0x07
sbi PORTB, 0x00
rcall irq_save_state
in Temp, UDR
in Temp2, UCSRA
bst Temp2, 0x04 ; store framing error
brts framing_error
; calculate jump table value

; first bounds check
lds Temp2, ADDR_NETSTATE
cpi Temp2, NETSTATE_MAX + 1; this value should always be one more than the number of network states
brsh urxc_overflow
; then calculate jump table value and execute
clc
ldi ZL, LOW(urxc_jmptable)
ldi ZH, HIGH(urxc_jmptable)
add ZL, Temp2
adc ZH, null

ijmp

urxc_overflow:
ldi Fault, FAULT_NETWORKSTATEOVERFLOW_RX
rcall fault
ldi Temp2, NETSTATE_IDLE
sts ADDR_NETSTATE, Temp2
rjmp urxc_end

framing_error:
; if we are transmitting, execute collission code
; if we are receiving, set rx_error
; otherwise, do nothing
lds Temp2, ADDR_NETSTATE
cpi Temp2, NETSTATE_TX
breq urxc_docol
cpi Temp2, NETSTATE_RX
brne urxc_fe_end
ldi Temp2, NETSTATE_RXERROR
sts ADDR_NETSTATE, Temp2
urxc_fe_end:
rjmp urxc_end

urxc_jmptable:
rjmp urxc_idle ; 0 = idle - arm, yes
rjmp urxc_tx ; 1 = tx - arm, yes
rjmp urxc_tx_collision ; 2 = tx_collision - arm, hell nono
rjmp urxc_rx	; 3 = rx - arm, yes
rjmp urxc_rx_wait ; 4 = rx_wait - arm, doesn't matter, fault condition
rjmp urxc_rx_error ; 5 = rx_error - arm, yes
rjmp urxc_tx_wait

urxc_idle:
; if network is idle, then we need to switch to RX mode because this is a incoming packet
; presumably the first packet is a discard, though, because it's blanking
; however, we still need to clear clear-to-send bit in network flags
; and also clear pending_cts
ldi Temp2, NETSTATE_RX
sts ADDR_NETSTATE, Temp2
clr Temp2
sts ADDR_RXPTR, Temp2
cbr net_flags, (1<<NETBIT_REQCTS) + (1<<NETBIT_GOTCTS)
rcall tim0_arm ; arm the timer
rjmp urxc_end
urxc_tx_wait:
; when you get to tx wait it means that the tx side is done transmitting
; and you must complete receiving. For convenience, we just increment
; the transmit pointer past NET_PACKET_LEN ;-)

; we could in theory continue to collision check, but waht would the point be?
lds Temp2, ADDR_TXPTR
cpi Temp2, NET_PACKET_LEN 
brsh urxc_tx_done
inc Temp2
sts ADDR_TXPTR, Temp2
rjmp urxc_end

urxc_tx_done:
rcall go_idle
clr Temp2
sts ADDR_TXPTR, Temp2
rjmp urxc_end

urxc_tx:
; if network is transmitting, we need to compare the incoming packet with the packet
; just transmitted. If they don't jibe, collision has occured and we must 
; generate a random number, stuff that random number into $B3, and set us to tx_collision state
; Temp should contain the bit just received
; we use the Z register to compute our current
; locale

lds Temp3, ADDR_TXPTR
cpi Temp3, 0x01
breq urxc_blanking ; TXPTR is zero, we're sending blanking

ldi ZL, ADDR_OUTGOINGBASE
clr ZH ; since we only have 128 bytes of ram
	; there isn't much risk of needing a high
	; order byte

ldi Temp2, 2
sub Temp3, Temp2
add ZL, Temp3

ld Temp2,Z ; temp2 now contains the char we thought
	    ; we sent, let's compare
cp Temp, Temp2
brne urxc_docol
; all is well with the world
rcall tim0_reset
rjmp urxc_end

urxc_blanking:
;ldi fault, FAULT_BLANKING ; debug
;rcall fault
ser Temp3
cp Temp, Temp3
brne urxc_docol2
rcall tim0_reset
rjmp urxc_end


urxc_docol2:
urxc_docol:
; if we're here, a collission has occured and
; we need to go to colission state
;sbi PORTD,0x07

ldi Temp2, NETSTATE_TXCOL
sts ADDR_NETSTATE, Temp2
; and we need to generate a random number
; and store it in the timer counter
rcall random ; beware this whacks temp & temp2
sts ADDR_NETTICKCTR, Temp
; the timer routine will call the retrans routine
; so there's no need to do anything with TX

ldi fault, FAULT_COLLISION ; debugging
rcall fault ; debugging

;cbi PORTD, 0x07
rjmp urxc_end

urxc_tx_collision:
; if we're here, a colission has occured [a miscompare of data coming off the wire with
; data being sent to the wire] and we're waiting for the colission to clear.

; presumably, if data comes incoming while this happens, since it is NOT us 
; [any urxe ints will not reload, and colission gets detected inside this int]
; it's got to be from another host, so it's the blanking packet at the head of a 
; message, discard it and go to RX state
; because our TX request bit is still set, eventually
; the main loop will retransmit our packet - 
; patience is a virtue
; in the meantime it's in the buffer, safe and sound
cbr net_flags, (1<<NETBIT_REQCTS) + (1<<NETBIT_GOTCTS)
ldi Temp, NETSTATE_RX
sts ADDR_NETSTATE, Temp
clr Temp
sts ADDR_RXPTR, Temp
rjmp urxc_end

urxc_rx:
; if we're here, we are receiving data, blanking has already been sent, so store data
; in incoming packet buffer after bounds checking it
; if this is the last packet, be sure and switch to rx_wait state
; also set the pending_cts flag, indicating we are waiting for a clear to send
; when the timer next fires, it will set clear to send

rcall tim0_reset
lds Temp2, ADDR_RXPTR
cpi Temp2, NET_PACKET_LEN +1
brsh urxc_packet_overflow
; Temp2 still contains receive pointer
; load the address register 


clr ZH
ldi ZL, ADDR_INCOMINGBASE
add ZL, Temp2
st Z, Temp ; store received data
inc Temp2
sts ADDR_RXPTR, Temp2 ; save the current location pointer for later

cpi Temp2, NET_PACKET_LEN
brlo urxc_end ; if we don't have a complete packet
	      ; if we do

;sbi PORTD, 0x07

;ldi fault, 42
;rcall fault

rcall req_cts
ldi Temp, NETSTATE_RXWAIT
sts ADDR_NETSTATE, Temp
rjmp urxc_end

urxc_rx_wait:
; if we're here, we're done receiving data and waiting for the main program loop
; to process the received data. If data comes in while we're in this mode, it's a oh shit
; situation. Send a fault notification.
ldi fault, FAULT_UNPROCESSEDRX
rcall fault
rjmp urxc_end

urxc_rx_error:
; if we're here, we've received a framing error or had a parity error whilst receiving
; We don't guarentee reliable transit, so we just discard the rest of the packet
rcall tim0_reset
rjmp urxc_end

urxc_packet_overflow:
; process overrun
; what the hell can you do here?
; basically, this means a packet came in
; that was too big for us
ldi Temp2, NETSTATE_RXERROR
sts ADDR_NETSTATE, Temp2
ldi fault, FAULT_URXC_OVERFLOW
rcall fault
rjmp urxc_end

urxc_end:

rcall irq_restore_state
;cbi PORTD, 0x07
cbi PORTB,0x00
reti

; ******************************************
; * UDRE_INT - data register empty interrupt
; ******************************************

udre_int: ; data register empty
;sbi PORTD,0x07

rcall irq_save_state
; first bounds check
lds Temp2, ADDR_NETSTATE
cpi Temp2, NETSTATE_MAX + 1; this value should always be the top net state
brsh udre_overflow
; then calculate jump table value and execute
clc
ldi ZL, LOW(udre_jmptable)
ldi ZH, HIGH(udre_jmptable)
add ZL, Temp2
adc ZH, null
ijmp

udre_jmptable:
rjmp udre_idle ; 0 = idle 
rjmp udre_tx ; 1 = tx 
rjmp udre_tx_collision ; 2 = tx_collision 
rjmp udre_rx	; 3 = rx 
rjmp udre_rx_wait ; 4 = rx_wait 
rjmp udre_rx_error ; 5 = rx_error 
rjmp udre_tx_wait 

udre_tx_wait: ; taht would scare me, okay?
udre_rx_error:
udre_rx_wait:
udre_rx:
udre_idle:
ldi fault, FAULT_UNEXPECTED_UDRE
add fault, Temp2
rcall udre_disarm
rjmp udre_fault


udre_tx:
; this is the expected place to end up at this interrupt. Basically, bounds check
; transmit pointer, if it's in bounds send a byte

lds Temp, ADDR_TXPTR
cpi Temp, NET_PACKET_LEN
brsh udre_done

clr ZH
ldi ZL, ADDR_OUTGOINGBASE
add ZL, Temp

ld Temp2,Z
out UDR, Temp2 ; send data

inc Temp
sts ADDR_TXPTR, Temp
cpi Temp, 1
breq udre_blanking
rjmp udre_end

udre_blanking:
rcall tim0_reset
rjmp udre_end

udre_done:
; this is kind of subtle
; if we got here, we are done transmitting. The receive interrupt should fire _before_ the udre 
; interrupt, but may not. If it doesn't, we don't sanity check the last byte! this is okay,
; since the last byte is a CRC and will sanity check itself ;-) but we still need
; to make sure the timer fires when it's supposed to
;
; the timer will disarm itself because we request clear to send

rcall udre_disarm
ldi Temp, NETSTATE_TXWAIT
sts ADDR_NETSTATE, Temp
rjmp udre_end

udre_tx_collision:
; if we are here, then a collision has been detected and we're in wait state for backoff
; so don't reload the buffer, dumb shit
rjmp udre_end

udre_overflow:
ldi fault, FAULT_NETWORKSTATEOVERFLOW_UDRE

udre_fault:
rcall fault
udre_end:
rcall irq_restore_state
;cbi PORTD, 0x07 ; debugging
reti


utxc_int:

; transmission complete interrupt
; not real sure what we'd do with this - UDRE is going to fire for the last char,
; and shortly thereafter RX is going to fire for the last char
; which will put us into CTS state
; then the CTS timer will fire, which will put us into ready-to-send-state
; the main loop will then note clear to send and transmit a packet if there is a packet
; pending for transmission [pending tx flag]

rcall irq_save_state
rcall irq_restore_state

reti

;**************************************************
;* OVF0_INT - Timer0 overflow - network timeout int
;**************************************************

ovf0_int:



; timer 0 overflow int
; because we reset the timer every byte,
; and disarm it when the packet is done being
; sent or received, this timer indicates a timeout 
; state

; different things ahve to be done depending on what
; the network state is at the time of timeout.
;
; if the network state is transmitting, we have to 
; abort the transmission and attempt to retrans
; unless we are above retrans-count

; if the network state is receiving, we have to abort
; the reception of this packet.
; 
; if the network state is collision-wait, we have
; to decrement the wait counter once for each time
; this fires, and when it hits zero, retransmit
; unless we are above retrans-count
; 
; note that it's possible we will get unarmed during
; collision-wait by a incoming byte, indicating that we lost ;-)
;
; in case of a collision we will hold onto our tx buffer
; until we finally get a chance to start senidng some data
; on a net with a lot of hosts this may take a while
sbi PORTD, 0x07
rcall irq_save_state
cbi PORTD, 0x07

; check to see if network flag pending CTS is set
; if not, go on to do the network jump table
; if so, we're waiting for pending CTS, when happens, clear pending CTS and set CTS
; when happens, also disarm timer
bst net_flags, NETBIT_REQCTS
brtc tim0_dojump
; we've got a REQCTS, so let's ack it
cbr net_flags, (1<<NETBIT_REQCTS)
sbr net_flags, (1<<NETBIT_GOTCTS)
;ldi fault, FAULT_GOTCTS ; debugging
;rcall fault ; debugging
rcall tim0_disarm
rjmp tim0_end

tim0_dojump:
lds Temp2, ADDR_NETSTATE
cpi Temp2, NETSTATE_MAX + 1 ; this value should always be one more than the number of network states
brsh tim0_overflow
; then calculate jump table value and execute
clc
ldi ZL, LOW(tim0_jmptable)
ldi ZH, HIGH(tim0_jmptable)
add ZL, Temp2
adc ZH, null
ijmp

tim0_jmptable:
rjmp tim0_idle ; 0 = idle - arm, yes
rjmp tim0_tx ; 1 = tx - arm, yes
rjmp tim0_tx_collision ; 2 = tx_collision - arm, yes
rjmp tim0_rx	; 3 = rx - arm, yes
rjmp tim0_rx_wait ; 4 = rx_wait - arm, doesn't matter, fault condition
rjmp tim0_rx_error ; 5 = rx_error - arm, yes
rjmp tim0_tx_wait ; 6 = tx_wait - arm,yes

tim0_rx_wait:
rcall go_idle
rjmp tim0_idle
tim0_tx_wait:
rcall go_tx_idle
rjmp tim0_idle
tim0_idle:
; timeout occured while idle? log fault
; timeout shouldn't ever be able to occur in rx_wait because
; we shouldn't be able to get this far
ldi fault, FAULT_UNEXPECTED_TIM0
add fault, temp2

rjmp tim0_fault

tim0_tx:

ldi fault, FAULT_TX_TIMEOUT ; debugging
rcall fault ; debugging

rcall go_tx_idle

; stripped down go_idle ;-)

; timeout occured while in transmit state? set pending CTS and return to idle state
; should we notify routine that transmission failed? Probebly not, as is unreliable network
; anyway. It's up to the receiver to ask for whatever they asked for again
rjmp tim0_end
tim0_tx_collision:
; time tick occured for colission. decrement tick counter. if tick counter is zero, return
; to tx state and rcall transmission initiation routine to retry transmission
lds Temp, ADDR_NETTICKCTR
dec Temp
sts ADDR_NETTICKCTR, Temp
brne tim0_end
rcall start_tx ; we kind of ahve to trust the main loop here not to
		; change something out from under us.
rjmp tim0_end

tim0_rx:
; timeout occured while in rx state? set pending CTS, reset incoming pointer, and return to
; idle state. we lost that packet anyway.

; fall through to

ldi fault, 0x09
rcall fault

lds fault, ADDR_RXPTR
rcall fault

tim0_rx_error:
; this is good news - means our corrupted packet is done being sent. set pending CTS, reset incoming
; ptr, and return to idle state.
rcall go_idle
rjmp tim0_end

tim0_overflow:
ldi fault, FAULT_NETWORKSTATEOVERFLOW_TIM0
tim0_fault:
rcall fault
tim0_end:
rcall irq_restore_state
reti

;************************
;* UART library routines
;************************

udre_arm:
in Temp, UCSRB
sbr Temp, (1<<UDRIE)
out UCSRB, Temp
ret

udre_disarm:
in Temp, UCSRB
cbr Temp, (1<<UDRIE)
out UCSRB, Temp
ret

;********************************************
;* TIM0 library routines
;********************************************

tim0_reset:
; reset the network timeout timer
; side effects: nukes Temp3
ldi Temp3, NET_CLKPRELOAD
out TCNT0, Temp3
ret

tim0_disarm:
; disarcm the network timeout timer
; also nukes temp3

in Temp3, TIMSK ; load the previous settings
cbr Temp3, 0x02 ; clear the bit for arming timer 0 int
out TIMSK, Temp3
clr Temp3

; think about stopping clock here
ret

tim0_arm:
; arm the network timeout timer
; side effects: nukes Temp3
in Temp3, TIFR
sbr Temp3, (1<<TOV0);
out TIFR, Temp3
in Temp3, TIMSK ; load the previous settings
sbr Temp3, 0x02 ; set the bit for arming timer 0 int
out TIMSK, Temp3 ; save the result
ldi Temp3, 0x02 ; 3.6864 Mhz
;ldi Temp3, 0x07 ; debug
out TCCR0, Temp3 ; start clock
ldi Temp3, NET_CLKPRELOAD
out TCNT0, Temp3

ret

;****************************************
;* IRQ state library routines
;****************************************

irq_save_state:
sts ADDR_TEMP_SWAP, Temp
sts ADDR_TEMP2_SWAP, Temp2
sts ADDR_TEMP3_SWAP, Temp3
sts ADDR_ZL_SWAP, ZL
sts ADDR_ZH_SWAP, ZH
in SregSwap, SREG
ret

irq_restore_state:
lds Temp, ADDR_TEMP_SWAP
lds Temp2, ADDR_TEMP2_SWAP
lds Temp3, ADDR_TEMP3_SWAP
lds ZL, ADDR_ZL_SWAP
lds ZH, ADDR_ZH_SWAP
out SREG, SregSwap
ret

;*****************************************
;* program memory library routines
;*****************************************

get_serial:
ldi ZL, low(2*PM_ADDR_SERIAL)  ; load the addressing register with the locatin of
ldi ZH, high(2*PM_ADDR_SERIAL) ; the serial number
rcall do_lpm
ret

get_crc:
ldi ZL, low(2*PM_ADDR_CRC) ; load the addressing register with the location of
ldi ZH, high(2*PM_ADDR_CRC) ; the CRC
rcall do_lpm
ret

build_crc:
; returns CRC in Temp3
clr ZL ; start building at zero
clr ZH
clr Temp3 ; clear the variable we are going to store our result in
build_crc_1:
lpm
add Temp3, lpm_reg ; this is allowed to roll indefinately - hell, we just want a CRC
add ZL, One ; we know we're still 0-255 because that's all we're allowed to use
	    ; for CRC-ignored data
	    ; hence, we can ignore the high order byte
	    
cpi ZL, low(2*PM_ADDR_STARTHOLE)
brne build_crc_1
build_crc_2:
; if we got here, we are in the black hole. Step until out
add ZL, One
cpi ZL, low(2*PM_ADDR_ENDHOLE)
brne build_crc_2
build_crc_3:
; if we got here, we are out of the black hole. Advance one to get over last step
clc
add ZL, One
adc ZH, Null
lpm
add Temp3, lpm_reg
cpi ZH, high(2*PM_ADDR_ENDMEM)
brne build_crc_3
cpi ZL, low(2*PM_ADDR_ENDMEM)
brne build_crc_3
ret

do_lpm:
; use care as these registers are shared with delay and delay2

lpm ; fetch the low byte
mov lpm1, lpm_reg ; copy it to register
sbr ZL,0x01 ; (the 2x guarantees we'll never set this bit above)
	    ; when we set the LSB, we end up reading the high byte of the word)    
lpm ; fetch the high byte
mov lpm2, lpm_reg; copy it to register
ret

;*****************************************
;* Fault handling library routines
;*****************************************

fault:

; for now, this just flashes the fault LED
; todo: add local EEPROM logging
; of the fault condition


;sbi PORTB, 0x00
sbi PORTD, PD5

out PORTC, fault

fault_dly:
sbi PORTD, PD4
cbi PORTD, PD4
dec Delay
brne fault_dly
dec Delay2
brne fault_dly ;debounce
in Temp3, PIND
sbrs Temp3, PD2
rjmp fault_dly

cbi PORTD, PD5
;dec Delay2
;brne fault_dly

; latch on current fault until input bit is toggled

out PORTC, fault ; debug
;cbi PORTB, 0x00
;fault_dly_2:
;dec Delay
;brne fault_dly_2
;dec Delay2
;brne fault_dly_2

ret

;*****************************************
;* Initilization library routines
;*****************************************

do_mem_init:
rcall eraseram
clr null ; setup null
ldi Temp, 0x01
mov One, Temp ; setup one
rcall get_serial
sts ADDR_RNDSEED_LO, lpm1
sts ADDR_RNDSEED_HI, lpm2

ret

do_io_init:
cbi DDRD, PD2 ; debugging turn on input for fault stop
sbi DDRD, PD5
sbi DDRD, PD4 ; debugging

sbi DDRB, 0x01 ; turn on output for pulldown resistor
cbi PORTB, 0x01 ; pull down pulldown resistor
; this might want to be exchanged for 'power up network section' - involves less parts, and
; is better behaved probebly ;-)
; otoh, this method lets us reset micro where that method might not in some circumstances
; hard to tell

ldi Temp, (1<<RXCIE) + (1<<RXEN) + (1<<TXEN)
out UCSRB, Temp

ldi Temp, 0x01 ; 0x03 for 7 Mhz
		; 0x01 for 3.6 Mhz
out UBRR, Temp ; 115 kbauds
sbi DDRD, 0x06 ; enable TX on pin 6 port D
sbi DDRD, 0x07 ; enable TX on pin 7 port D
ser Temp
out DDRC, Temp ; enable TX on all port C pins
out PORTC, null ; clear all PORTC lines
sbi DDRB, 0x00 ; turn on output for fault LED
cbi PORTB, 0x00 ; turn off fault LED
cbi PORTD, 0x07 ; turn off operating LED
rcall tim0_disarm

ret

eraseram:
ldi ZL, ADDR_LOERASE
clr ZH
init_eraseram:
cpi ZL, ADDR_HIERASE
st Z+, null
brlo init_eraseram
ret


;**************************************
;* network library routines 
;**************************************

random:
; side effects: clobbers temp & temp2
; clobbers carry flags
; clobbers anything in the mult/div registers

lds mc16uL, ADDR_RNDSEED_LO
lds mc16uH, ADDR_RNDSEED_HI
ldi Temp, low(1001)
ldi Temp2, high(1001)
mov mp16uL, Temp
mov mp16uH, Temp2
rcall mpy16u
ldi Temp, low(501)
ldi Temp2, high(501)
clc
add m16u0, Temp
adc m16u1, Temp2
sts ADDR_RNDSEED_LO, m16u0
sts ADDR_RNDSEED_HI, m16u1
mov Temp, m16u0
ret

build_packet_postfix:
; insert CRC of our packet
clr Temp
clr Temp2
clr Temp3
build_packet_postfix_1:
clr ZH
ldi ZL, ADDR_OUTGOINGBASE
add ZL, Temp2
ld Temp3, Z

inc Temp2
lds Temp, ADDR_OUTGOINGBASE + NETOFFSET_CRC
clc
add Temp, Temp3
sts ADDR_OUTGOINGBASE + NETOFFSET_CRC, Temp
lds Temp, ADDR_OUTGOINGBASE + NETOFFSET_CRC + 1
adc Temp, null
sts ADDR_OUTGOINGBASE + NETOFFSET_CRC + 1, temp
cpi Temp2, NET_PACKET_LEN - 1
brne build_packet_postfix_1
ret


build_packet_prefix:
; insert src addr of our addr
; insert dest addr of transmitted dest addr
; the few cmds that don't need this [LCD transmit, master elect]
; will just overwrite it with what they do need
rcall get_serial
sts ADDR_OUTGOINGBASE + NETOFFSET_SRCADDR, lpm1
sts ADDR_OUTGOINGBASE + NETOFFSET_SRCADDR + 1, lpm1
ldi Temp, ADDR_INCOMINGBASE + NETOFFSET_SRCADDR
ldi Temp2, ADDR_INCOMINGBASE + NETOFFSET_SRCADDR + 1
sts ADDR_OUTGOINGBASE + NETOFFSET_DESTADDR, Temp
sts ADDR_OUTGOINGBASE + NETOFFSET_DESTADDR + 1, temp2
ret


req_cts:
cbr net_flags, (1<<NETBIT_GOTCTS)
sbr net_flags, (1<<NETBIT_REQCTS)
ret

start_tx:
; reset tx pointer
ldi Temp, NETSTATE_TX
sts ADDR_NETSTATE,Temp
clr Temp
cbr Net_Flags, (1<<NETBIT_REQTX)
sts ADDR_TXPTR, Temp
; send blanking packet
ser Temp
out UDR, Temp
; arm the timer
rcall tim0_arm
rcall udre_arm

; sit back and watch the fun
ret

go_tx_idle:
rcall req_cts
clr Temp
sts ADDR_TXPTR, Temp
ldi Temp, NETSTATE_IDLE
sts ADDR_NETSTATE, Temp
rcall tim0_reset
ret

go_idle_txready:
sbr net_flags, (1<<NETBIT_REQTX)

go_idle:
rcall req_cts
clr Temp
sts ADDR_RXPTR, Temp
ldi Temp, NETSTATE_IDLE
sts ADDR_NETSTATE, Temp
rcall tim0_arm ; just in case
		; used to be reset
ret

;***************************************
;* SVC_NET - contains among other things
;* the incoming packet handler
;* Should be called periodically by the main
;* loop - hopefully called once between each
;* incoming packet
;* BUT NOT BY THE INT HANDLER !
;***************************************
 
svc_net:
; check to see if network state is rx_wait

sbi PORTC,0x04
nop
cbi PORTC,0x04 ; heartbeat

lds Temp, ADDR_NETSTATE

cpi Temp, NETSTATE_IDLE
brne svc_net_2
sbi PORTC, 0x05
svc_net_2:
cbi PORTC, 0x05
cpi Temp, NETSTATE_RXWAIT
breq svc_net_rxwait
; check to see if we've got a transmission request pending

bst net_flags, NETBIT_REQTX
brtc svc_net_end
; check to see if we've got clearance to transmit

bst net_flags, NETBIT_GOTCTS
brtc svc_net_end
; check and make sure network is idle

cpi Temp, NETSTATE_IDLE
brne svc_net_end
sbi PORTB, 0x00

rcall start_tx ; begin transmission
cbi PORTB, 0x00
rjmp svc_net_end
svc_net_rxwait:
; *******************************************
; * the all inportant incoming packet handler
; * first, check and see if packet is bcast
; *******************************************

cbr Net_flags, (1<<NETBIT_BCAST)
lds Temp, ADDR_INCOMINGBASE + NETOFFSET_DESTADDR
cpi Temp, NETADDR_BCASTLO
brne svc_net_notbroadcast 
lds Temp2, ADDR_INCOMINGBASE + NETOFFSET_DESTADDR + 1
cpi Temp2, NETADDR_BCASTHI
brne svc_net_notbroadcast
sbr Net_flags, (1<<NETBIT_BCAST)
rjmp to_us
svc_net_notbroadcast:
; check to see if this packet is to us
; Temp already contains the lo order byte of the net addr
; so we need to load the high order byte
lds Temp2, ADDR_INCOMINGBASE + NETOFFSET_DESTADDR + 1
rcall get_serial
cp Temp, lpm1
brne svc_net_not_to_us
cp Temp2, lpm2
brne svc_net_not_to_us
to_us:

; if we made it here, the packet is to us
; or broadcast. Either way, we have to
; respond to it
; we just use a jump table for cmd
; but first we must bounds check
lds Temp, ADDR_INCOMINGBASE + NETOFFSET_CMD
cpi Temp, NET_MAXCMD + 1
brsh svc_net_unknown_cmd
ldi ZL, low(svc_net_rx_cmd_jumptable)
ldi ZH, high(svc_net_rx_cmd_jumptable)
clc
add ZL, Temp
adc ZH, null
ijmp

svc_net_rx_cmd_jumptable:
rjmp svc_net_rx_cmd_nop
rjmp svc_net_rx_cmd_ping

svc_net_rx_cmd_nop:
rcall go_idle
rjmp svc_net_end

svc_net_rx_cmd_ping:

rcall build_packet_prefix
ldi Temp, NETCMD_PING
sts ADDR_OUTGOINGBASE + NETOFFSET_CMD, Temp
rcall build_packet_postfix
rcall go_idle_txready

rjmp svc_net_end

svc_net_unknown_cmd:
ldi fault, FAULT_UNKNOWN_NETCMD
add fault, Temp
rcall fault
rjmp svc_net_end

svc_net_not_to_us:
ldi fault, FAULT_NOTTOUS
rcall fault
rcall go_idle
svc_net_end:
ret

; ******************************
; * math library routines
; * courtesy of Atmel App Notes
; ******************************

mpy16u:	clr	m16u3		;clear 2 highest bytes of result
	clr	m16u2
	ldi	mcnt16u,16	;init loop counter
	lsr	mp16uH
	ror	mp16uL

m16u_1:	brcc	noad8		;if bit 0 of multiplier set
	add	m16u2,mc16uL	;add multiplicand Low to byte 2 of res
	adc	m16u3,mc16uH	;add multiplicand high to byte 3 of res
noad8:	ror	m16u3		;shift right result byte 3
	ror	m16u2		;rotate right result byte 2
	ror	m16u1		;rotate result byte 1 and multiplier High
	ror	m16u0		;rotate result byte 0 and multiplier Low
	dec	mcnt16u		;decrement loop counter
	brne	m16u_1		;if not done, loop more
	ret
