From 6d88a6a03ebcd68ab66bc99589d5266d4a834620 Mon Sep 17 00:00:00 2001 From: jonnosan Date: Thu, 22 Jan 2009 01:27:03 +0000 Subject: [PATCH] git-svn-id: http://svn.code.sf.net/p/netboot65/code@15 93682198-c243-4bdb-bd91-e943c89aac3b --- client/cfg/a2bin.cfg | 19 + client/cfg/a2language_card.cfg | 22 ++ client/cfg/c64prg.cfg | 16 + client/cfg/rrbin.cfg | 20 + client/clients/Makefile | 44 +++ client/clients/fix_cart.rb | 19 + client/clients/hello | Bin 0 -> 256 bytes client/clients/rrnetboot.s | 163 ++++++++ client/clients/utherboot.s | 236 ++++++++++++ client/doc/BOOTSTRAPPING.txt | 15 + client/doc/apple_dos_33_memory_layout.txt | 46 +++ client/drivers/Makefile | 32 ++ client/drivers/a2kernal.s | 5 + client/drivers/a2print.s | 21 + client/drivers/a2timer.s | 40 ++ client/drivers/c64kernal.s | 5 + client/drivers/c64print.s | 13 + client/drivers/c64timer.s | 46 +++ client/drivers/rr-net.s | 35 ++ client/drivers/uthernet.s | 29 ++ client/inc/common.i | 17 + client/inc/commonprint.i | 232 +++++++++++ client/inc/net.i | 41 ++ client/inc/petscii.i | 22 ++ client/inc/printf.i | 55 +++ client/ip65/Makefile | 43 +++ client/ip65/arp.s | 448 ++++++++++++++++++++++ client/ip65/config.s | 21 + client/ip65/copymem.s | 54 +++ client/ip65/cs8900a.i | 7 + client/ip65/cs8900a.s | 275 +++++++++++++ client/ip65/debug.s | 130 +++++++ client/ip65/dhcp.s | 448 ++++++++++++++++++++++ client/ip65/dns.s | 414 ++++++++++++++++++++ client/ip65/eth.s | 55 +++ client/ip65/icmp.s | 226 +++++++++++ client/ip65/ip.s | 410 ++++++++++++++++++++ client/ip65/ip65.s | 76 ++++ client/ip65/printf.s | 396 +++++++++++++++++++ client/ip65/tftp.s | 357 +++++++++++++++++ client/ip65/timer.s | 38 ++ client/ip65/udp.s | 313 +++++++++++++++ client/test/Makefile | 45 +++ client/test/testdns.s | 136 +++++++ 44 files changed, 5085 insertions(+) create mode 100644 client/cfg/a2bin.cfg create mode 100644 client/cfg/a2language_card.cfg create mode 100644 client/cfg/c64prg.cfg create mode 100644 client/cfg/rrbin.cfg create mode 100644 client/clients/Makefile create mode 100644 client/clients/fix_cart.rb create mode 100644 client/clients/hello create mode 100644 client/clients/rrnetboot.s create mode 100644 client/clients/utherboot.s create mode 100644 client/doc/BOOTSTRAPPING.txt create mode 100644 client/doc/apple_dos_33_memory_layout.txt create mode 100644 client/drivers/Makefile create mode 100644 client/drivers/a2kernal.s create mode 100644 client/drivers/a2print.s create mode 100644 client/drivers/a2timer.s create mode 100644 client/drivers/c64kernal.s create mode 100644 client/drivers/c64print.s create mode 100644 client/drivers/c64timer.s create mode 100644 client/drivers/rr-net.s create mode 100644 client/drivers/uthernet.s create mode 100644 client/inc/common.i create mode 100644 client/inc/commonprint.i create mode 100644 client/inc/net.i create mode 100644 client/inc/petscii.i create mode 100644 client/inc/printf.i create mode 100644 client/ip65/Makefile create mode 100644 client/ip65/arp.s create mode 100644 client/ip65/config.s create mode 100644 client/ip65/copymem.s create mode 100644 client/ip65/cs8900a.i create mode 100644 client/ip65/cs8900a.s create mode 100644 client/ip65/debug.s create mode 100644 client/ip65/dhcp.s create mode 100644 client/ip65/dns.s create mode 100644 client/ip65/eth.s create mode 100644 client/ip65/icmp.s create mode 100644 client/ip65/ip.s create mode 100644 client/ip65/ip65.s create mode 100644 client/ip65/printf.s create mode 100644 client/ip65/tftp.s create mode 100644 client/ip65/timer.s create mode 100644 client/ip65/udp.s create mode 100644 client/test/Makefile create mode 100644 client/test/testdns.s diff --git a/client/cfg/a2bin.cfg b/client/cfg/a2bin.cfg new file mode 100644 index 0000000..68fbc04 --- /dev/null +++ b/client/cfg/a2bin.cfg @@ -0,0 +1,19 @@ + +MEMORY { + ZP: start = $00, size = $08, type = rw, define = yes; + IP65ZP: start = $0f, size = $10, type = rw, define = yes; + HEADER: start = $0000, size = $10, file = %O; + RAM: start = $800, size = $8000, file = %O; +} +SEGMENTS { + EXEHDR: load = HEADER, type = ro; + STARTUP: load = RAM,run=RAM, type = ro, define = yes; + CODE: load = RAM, run=RAM, type = ro, define = yes; + RODATA: load = RAM, run=RAM, type = ro , define = yes; + DATA: load = RAM, run=RAM, type = rw , define = yes; + BSS: load=RAM, type = bss, define = yes; + ZEROPAGE: load = ZP, type = zp; + IP65ZP: load = IP65ZP, type = zp; +} + + diff --git a/client/cfg/a2language_card.cfg b/client/cfg/a2language_card.cfg new file mode 100644 index 0000000..ff8dd2b --- /dev/null +++ b/client/cfg/a2language_card.cfg @@ -0,0 +1,22 @@ + +MEMORY { + ZP: start = $00, size = $08, type = rw, define = yes; + IP65ZP: start = $0f, size = $10, type = rw, define = yes; + HEADER: start = $0000, size = $4, file = %O; + RAM: start = $800, size = $8000, file = %O; + LANGUAGE_CARD: start= $D000, size=$2800, type=rw, define=yes; + PAGE3: start = $301, size = 20; +} +SEGMENTS { + EXEHDR: load = HEADER, type = ro; + STARTUP: load = RAM,run=RAM, type = ro, define = yes; + CODE: load = RAM, run=LANGUAGE_CARD, type = ro, define = yes; + RODATA: load = RAM, run=LANGUAGE_CARD, type = ro , define = yes; + DATA: load = RAM, run=LANGUAGE_CARD, type = rw , define = yes; + BSS: load=LANGUAGE_CARD, type = bss, define = yes; + ZEROPAGE: load = ZP, type = zp; + IP65ZP: load = IP65ZP, type = zp; + PAGE3: load=PAGE3,type=bss; +} + + diff --git a/client/cfg/c64prg.cfg b/client/cfg/c64prg.cfg new file mode 100644 index 0000000..8ec783d --- /dev/null +++ b/client/cfg/c64prg.cfg @@ -0,0 +1,16 @@ +MEMORY { + ZP: start = $02, size = $1A, type = rw, define = yes; + IP65ZP: start = $5f, size = $10, type = rw, define = yes; + RAM: start = $07FF, size = $c801, define = yes, file = %O; + DISCARD: start = $77FF, size = $10, define = yes; +} +SEGMENTS { + STARTUP: load = RAM, type = ro ,define = yes; + CODE: load = RAM, type = ro,define = yes; + DATA: load = RAM, type = rw,define = yes; + RODATA: load = RAM, type = ro,define = yes; + BSS: load = RAM, type = bss; + ZEROPAGE: load = ZP, type = zp; + IP65ZP: load = IP65ZP, type = zp; + EXEHDR: load = DISCARD, type = ro; +} diff --git a/client/cfg/rrbin.cfg b/client/cfg/rrbin.cfg new file mode 100644 index 0000000..9909924 --- /dev/null +++ b/client/cfg/rrbin.cfg @@ -0,0 +1,20 @@ +MEMORY { + ZP: start = $02, size = $1A, type = rw, define = yes; + IP65ZP: start = $5f, size = $10, type = rw, define = yes; + HEADER: start = $0000, size = $9, file = %O; + ROM: start = $8009, size = $1FF7, define = yes, file = %O; + RAM: start = $6000, size = $2000, define = yes; + + +} +SEGMENTS { + CARTRIDGE_HEADER: load = HEADER, type = ro; + CODE: load = ROM, type = ro; + RODATA: load = ROM, type = ro; + DATA: load = ROM, run = RAM, type = rw, define = yes; + BSS: load = RAM, type = bss; + IP65ZP: load = IP65ZP, type = zp; + ZEROPAGE: load = ZP, type = zp; + + +} diff --git a/client/clients/Makefile b/client/clients/Makefile new file mode 100644 index 0000000..d70cc5c --- /dev/null +++ b/client/clients/Makefile @@ -0,0 +1,44 @@ +CC=cl65 +AS=ca65 +LD=ld65 +CFLAGS=-Oirs -t $(TARGET) +AFLAGS= + +%.o: %.c + $(CC) -c $(CFLAGS) $< + +%.o: %.s ../inc/print.i + $(AS) $(AFLAGS) $< + +IP65LIB=../ip65/ip65.lib + +C64NETLIB=../drivers/c64net.lib +APPLE2NETLIB=../drivers/apple2net.lib + +INCFILES=\ + ../inc/common.i\ + ../inc/commonprint.i\ + ../inc/net.i\ + +all: \ + rrnetboot.bin \ + utherboot.dsk \ + +rrnetboot.bin: rrnetboot.o $(IP65LIB) $(C64NETLIB) $(INCFILES) ../cfg/rrbin.cfg + $(LD) -m rrnetboot.map -C ../cfg/rrbin.cfg -o rrnetboot.bin $(AFLAGS) $< $(IP65LIB) $(C64NETLIB) + ruby fix_cart.rb rrnetboot.bin + +utherboot.bin: utherboot.o $(IP65LIB) $(APPLE2NETLIB) $(INCFILES) ../cfg/a2language_card.cfg + $(LD) -m utherboot.map -C ../cfg/a2language_card.cfg -o utherboot.bin $(AFLAGS) $< $(IP65LIB) $(APPLE2NETLIB) + +utherboot.dsk: utherboot.bin + dsktool.rb --init dos33 utherboot.dsk -a utherboot.bin -t B + dsktool.rb utherboot.dsk -a hello -t A + +clean: + rm -f *.o + rm -f rrnetboot.bin rrnetboot.map + rm -f utherboot.bin utherboot.map utherboot.dsk + +distclean: clean + rm -f *~ diff --git a/client/clients/fix_cart.rb b/client/clients/fix_cart.rb new file mode 100644 index 0000000..c9d5b4e --- /dev/null +++ b/client/clients/fix_cart.rb @@ -0,0 +1,19 @@ +# +# Vice will treat a cartridge bin file that is of an even length as if the first 2 bytes in the file are a load address to be skipped over +# so we want to make sure the bin file is an odd length - specifically 8193 bytes +# + +FILE_LENGTH=8193 +PAD_BYTE=0xff.chr +filename=ARGV[0] +if filename.nil? then + puts "no filename specified" + exit +end + +puts "fixing length of #{filename} to #{FILE_LENGTH} bytes" +infile=File.open(filename,"rb").read +outfile=File.open(filename,"wb") +outfile<(.right (.tcount (arg)-1, arg)) + .else ; assume absolute or zero page + lda arg + ldx 1+(arg) + .endif + .endmacro + +; store A/X macro + .macro stax arg + sta arg + stx 1+(arg) + .endmacro + diff --git a/client/inc/commonprint.i b/client/inc/commonprint.i new file mode 100644 index 0000000..09b875c --- /dev/null +++ b/client/inc/commonprint.i @@ -0,0 +1,232 @@ + .zeropage +pptr: .res 2 +.bss +temp_bin: .res 1 +temp_bcd: .res 2 + +.code +.macro print_driver_init + ldax #cs_driver_name + jsr print + ldax #init_msg + jsr print +.endmacro + + +.macro print_dhcp_init + ldax #dhcp_msg + jsr print + ldax #init_msg + jsr print +.endmacro + +.macro print_failed + ldax #failed_msg + jsr print + jsr print_cr +.endmacro + +.macro print_ok + ldax #ok_msg + jsr print + jsr print_cr +.endmacro + + +.code + +.import print_a +.import print_cr +.import cs_driver_name + +print_ip_config: + ldax #ip_address_msg + jsr print + ldax #cfg_ip + jsr print_dotted_quad + jsr print_cr + + ldax #netmask_msg + jsr print + ldax #cfg_netmask + jsr print_dotted_quad + jsr print_cr + + ldax #gateway_msg + jsr print + ldax #cfg_gateway + jsr print_dotted_quad + jsr print_cr + + ldax #dns_server_msg + jsr print + ldax #cfg_dns + jsr print_dotted_quad + jsr print_cr + + ldax #dhcp_server_msg + jsr print + ldax #dhcp_server + jsr print_dotted_quad + jsr print_cr + + ldax #tftp_server_msg + jsr print + ldax #cfg_tftp_server + jsr print_dotted_quad + jsr print_cr + + rts + +print: + sta pptr + stx pptr + 1 + +@print_loop: + ldy #0 + lda (pptr),y + beq @done_print + jsr print_a + inc pptr + bne @print_loop + inc pptr+1 + bne @print_loop ;if we ever get to $ffff, we've probably gone far enough ;-) +@done_print: + rts + + +;print the 4 bytes pointed at by AX as dotted decimals +print_dotted_quad: + sta pptr + stx pptr + 1 + ldy #0 + lda (pptr),y + jsr print_decimal + lda #'.' + jsr print_a + + ldy #1 + lda (pptr),y + jsr print_decimal + lda #'.' + jsr print_a + + ldy #2 + lda (pptr),y + jsr print_decimal + lda #'.' + jsr print_a + + ldy #3 + lda (pptr),y + jsr print_decimal + + rts + +print_decimal: ;print byte in A as a decimal number + pha + sta temp_bin ;save + sed ; Switch to decimal mode + lda #0 ; Ensure the result is clear + sta temp_bcd + sta temp_bcd+1 + ldx #8 ; The number of source bits + : + asl temp_bin+0 ; Shift out one bit + lda temp_bcd+0 ; And add into result + adc temp_bcd+0 + sta temp_bcd+0 + lda temp_bcd+1 ; propagating any carry + adc temp_bcd+1 + sta temp_bcd+1 + dex ; And repeat for next bit + bne :- + + cld ;back to binary + + pla ;get back the original passed in number + bmi @print_hundreds ; if N is set, the number is >=128 so print all 3 digits + cmp #10 + bmi @print_units + cmp #100 + bmi @print_tens +@print_hundreds: + lda temp_bcd+1 ;get the most significant digit + and #$0f + clc + adc #'0' + jsr print_a + +@print_tens: + lda temp_bcd + lsr + lsr + lsr + lsr + clc + adc #'0' + jsr print_a +@print_units: + lda temp_bcd + and #$0f + clc + adc #'0' + jsr print_a + + rts + + +print_hex: + pha + pha + lsr + lsr + lsr + lsr + tax + lda hexdigits,x + jsr print_a + pla + and #$0F + tax + lda hexdigits,x + jsr print_a + pla + rts + +.rodata +hexdigits: +.byte "0123456789ABCDEF" + +ip_address_msg: + .byte "IP ADDRESS: ", 0 + +netmask_msg: + .byte "NETMASK: ", 0 + +gateway_msg: + .byte "GATEWAY: ", 0 + +dns_server_msg: + .byte "DNS SERVER: ", 0 + +dhcp_server_msg: + .byte "DHCP SERVER:", 0 + +tftp_server_msg: + .byte "TFTP SERVER: ", 0 + +dhcp_msg: + .byte "DHCP",0 + +init_msg: + .byte " INIT ",0 + +failed_msg: + .byte "FAILED", 0 + +ok_msg: + .byte "OK", 0 + +dns_lookup_failed_msg: + .byte "DNS LOOKUP FAILED", 0 diff --git a/client/inc/net.i b/client/inc/net.i new file mode 100644 index 0000000..cf4f499 --- /dev/null +++ b/client/inc/net.i @@ -0,0 +1,41 @@ + .import ip65_init + .import ip65_process + + .import cfg_mac + .import cfg_ip + .import cfg_netmask + .import cfg_gateway + .import cfg_dns + .import cfg_tftp_server + + .import dhcp_init + .import dhcp_server + + +.macro init_ip_via_dhcp + + print_driver_init + jsr ip65_init + bcc :+ + print_failed + sec + jmp @end_macro + +: + + print_ok + + print_dhcp_init + + jsr dhcp_init + bcc :+ + + + print_failed + sec + rts +: + print_ok + clc +@end_macro: +.endmacro \ No newline at end of file diff --git a/client/inc/petscii.i b/client/inc/petscii.i new file mode 100644 index 0000000..cd2d9a4 --- /dev/null +++ b/client/inc/petscii.i @@ -0,0 +1,22 @@ +petscii_black = 144 +petscii_white = 5 +petscii_red = 28 +petscii_cyan = 159 +petscii_purple = 156 +petscii_green = 30 +petscii_blue = 31 +petscii_yellow = 158 +petscii_orange = 129 +petscii_brown = 149 +petscii_ltred = 150 +petscii_dkgray = 151 +petscii_gray = 152 +petscii_ltgreen = 153 +petscii_ltblue = 154 +petscii_ltgray = 155 + +petscii_lower = 14 +petscii_home = 19 +petscii_clear = 147 + +petscii_down = 17 diff --git a/client/inc/printf.i b/client/inc/printf.i new file mode 100644 index 0000000..0aa435f --- /dev/null +++ b/client/inc/printf.i @@ -0,0 +1,55 @@ + .import console_printf + + + .macro printfargs arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 + .ifnblank arg1 + .addr arg1 + printfargs arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 + .endif + .endmacro + + .macro printf str, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 + + .local arglist + .local string + + pha + .ifpc02 + phx + phy + .else + txa + pha + tya + pha + .endif + ldax #arglist + jsr console_printf + .ifpc02 + ply + plx + .else + pla + tay + pla + tax + .endif + pla + + .pushseg + .rodata + .if (.match(str, "")) +string: + .asciiz str +arglist: + .addr string + .else +arglist: + .addr str + .endif + + printfargs arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 + + .popseg + + .endmacro diff --git a/client/ip65/Makefile b/client/ip65/Makefile new file mode 100644 index 0000000..54966e2 --- /dev/null +++ b/client/ip65/Makefile @@ -0,0 +1,43 @@ +AS=ca65 +LD=ld65 +AFLAGS= + + +%.o: %.c + $(CC) -c $(CFLAGS) $< + +%.o: %.s + $(AS) $(AFLAGS) $< + + +ETHOBJS= \ + copymem.o \ + config.o \ + timer.o \ + cs8900a.o \ + eth.o \ + arp.o \ + ip.o \ + icmp.o \ + udp.o \ + ip65.o \ + printf.o \ + debug.o \ + dhcp.o \ + dns.o \ + tftp.o \ + +all: ip65.lib + + +ip65.lib: $(ETHOBJS) + ar65 a ip65.lib $^ + + +clean: + rm -f *.o + rm -f ip65.lib + + +distclean: clean + rm -f *~ diff --git a/client/ip65/arp.s b/client/ip65/arp.s new file mode 100644 index 0000000..95e9427 --- /dev/null +++ b/client/ip65/arp.s @@ -0,0 +1,448 @@ +; ARP address resolution + +;originally from Per Olofsson's IP65 library - http://www.paradroid.net/ip65 + +.include "../inc/common.i" + + .export arp_init + .export arp_lookup + .export arp_process + .export arp_add + + .export arp_ip + .export arp_mac + .export arp_cache + + .import eth_inp + .import eth_inp_len + .import eth_outp + .import eth_outp_len + .import eth_tx + .import eth_set_broadcast_dest + .import eth_set_my_mac_src + .import eth_set_proto + .importzp eth_proto_arp + + .import cfg_mac + .import cfg_ip + .import cfg_netmask + .import cfg_gateway + + .import timer_read + .import timer_timeout + + + .segment "IP65ZP" : zeropage + +ap: .res 2 + + + .bss + +; arp state machine +arp_idle = 1 ; idling +arp_wait = 2 ; waiting for reply +arp_state: .res 1 ; current activity + +; arguments for lookup and add +arp: ; ptr to mac/ip pair +arp_mac: .res 6 ; result is delivered here +arp_ip: .res 4 ; set ip before calling lookup + +; arp cache +ac_size = 8 ; lookup cache +ac_ip = 6 ; offset for ip +ac_mac = 0 ; offset for mac +arp_cache: .res (6+4)*ac_size + +; offsets for arp packet generation +ap_hw = 14 ; hw type (eth = 0001) +ap_proto = 16 ; protocol (ip = 0800) +ap_hwlen = 18 ; hw addr len (eth = 06) +ap_protolen = 19 ; proto addr len (ip = 04) +ap_op = 20 ; request = 0001, reply = 0002 +ap_shw = 22 ; sender hw addr +ap_sp = 28 ; sender proto addr +ap_thw = 32 ; target hw addr +ap_tp = 38 ; target protoaddr +ap_packlen = 42 ; total length of packet + +; gateway handling +gw_mask: .res 4 ; inverted netmask +gw_test: .res 4 ; gateway ip or:d with inverted netmask +gw_last: .res 1 ; netmask length - 1 + +; timeout +arptimeout: .res 2 ; time when we will have timed out + + + .code + +; initialize arp +arp_init: + lda #0 + + ldx #(6+4)*ac_size - 1 ; clear cache +: sta arp_cache,x + dex + bpl :- + + lda #$ff ; counter for netmask length - 1 + sta gw_last + + ldx #3 +@gw: + lda cfg_netmask,x + eor #$ff + cmp #$ff + bne :+ + inc gw_last +: sta gw_mask,x + ora cfg_gateway,x + sta gw_test,x + dex + bpl @gw + + lda #arp_idle ; start out idle + sta arp_state + + rts + + +; lookup an ip +; clc = mac returned in arp_mac +; sec = request sent, call again for result +arp_lookup: + + lda arp_ip ; check for broadcast IP (255.255.255.255) + and arp_ip + 1 + and arp_ip + 2 + and arp_ip + 3 + cmp #$ff + bne @notbroadcast + ldx #6 ;copy ff:ff:ff:ff:ff:ff to ap_mac +: sta arp_mac,x + dex + bpl :- + clc + rts + +@notbroadcast: + + ldx gw_last ; check if address is on our subnet +: lda arp_ip,x + ora gw_mask,x + cmp gw_test,x + bne @notlocal + dex + bpl :- + bmi @local + +@notlocal: + + ldx #3 ; copy gateway's ip address +: lda cfg_gateway,x + sta arp_ip,x + dex + bpl :- + +@local: + jsr findip + bcs @cachemiss + + ldy #ac_ip - 1 ; copy mac +: lda (ap),y + sta arp,y + dey + bpl :- + rts + +@cachemiss: + lda arp_state ; are we already waiting for a reply? + cmp #arp_idle + beq @sendrequest ; yes, send request + + ldax arptimeout ; check if we've timed out + jsr timer_timeout + bcs @notimeout ; no, don't send + +@sendrequest: ; send out arp request + jsr eth_set_broadcast_dest + jsr eth_set_my_mac_src + + jsr makearppacket ; add arp, eth, ip, hwlen, protolen + + lda #0 ; set opcode (request = 0001) + sta eth_outp + ap_op + lda #1 + sta eth_outp + ap_op + 1 + + ldx #5 +: lda cfg_mac,x ; set source mac addr + sta eth_outp + ap_shw,x + lda #0 ; set target mac addr + sta eth_outp + ap_thw,x + dex + bpl :- + + ldx #3 +: lda cfg_ip,x ; set source ip addr + sta eth_outp + ap_sp,x + lda arp_ip,x ; set target ip addr + sta eth_outp + ap_tp,x + dex + bpl :- + + lda #ap_packlen + sta eth_outp_len + 1 + + jsr eth_tx ; send packet + + lda #arp_wait ; waiting for reply + sta arp_state + + jsr timer_read ; read current timer value + clc + adc #<1000 ; set timeout to now + 1000 ms + sta arptimeout + txa + adc #>1000 + sta arptimeout + 1 + +@notimeout: + sec ; set carry to indicate that + rts ; no result is availble + + +; find arp_ip in the cache +; clc returns pointer to entry in (ap) +findip: + ldax #arp_cache + stax ap + + ldx #ac_size +@compare: ; compare cache entry + ldy #ac_ip + lda (ap),y + beq @notfound +: lda (ap),y + cmp arp,y + bne @next + iny + cpy #ac_ip + 4 + bne :- + + clc ; return + rts + +@next: ; next entry + lda ap + clc + adc #10 + sta ap + bcc :+ + inc ap + 1 +: dex + bne @compare + +@notfound: + sec + rts + + +; handle incoming arp packets +arp_process: +; lda eth_inp_len ; check packet size +; cmp #ap_packlen +; bne @badpacket + + lda eth_inp + ap_op ; should be 0 + bne @badpacket + lda eth_inp + ap_op + 1 ; check opcode + cmp #1 ; request? + beq @request + cmp #2 ; reply? + beq @reply + +@badpacket: + sec + rts + +@request: + ldx #3 +: lda eth_inp + ap_tp,x ; check if they're asking for + cmp cfg_ip,x ; my address + bne @done + dex + bpl :- + + ldax #eth_inp + ap_shw + jsr ac_add_source ; add them to arp cache + + ldx #5 ; send reply +: lda eth_inp + ap_shw,x + sta eth_outp,x ; set sender packet dest + sta eth_outp + ap_thw,x ; and as target + lda cfg_mac,x ; me as source + sta eth_outp + ap_shw,x + dex + bpl :- + + jsr eth_set_my_mac_src ; me as packet source + + jsr makearppacket ; add arp, eth, ip, hwlen, protolen + + lda #0 ; set opcode (reply = 0002) + sta eth_outp + ap_op + lda #2 + sta eth_outp + ap_op + 1 + + ldx #3 +: lda eth_inp + ap_sp,x ; sender as target addr + sta eth_outp + ap_tp,x + lda cfg_ip,x ; my ip as source addr + sta eth_outp + ap_sp,x + dex + bpl :- + + lda #ap_packlen + sta eth_outp_len + 1 + + jsr eth_tx ; send packet + +@done: + clc + rts + +@reply: + lda arp_state + cmp #arp_wait ; are we waiting for a reply? + bne @badpacket + +; ldx #0 +;: lda gotmsg,x +; beq :+ +; jsr $ffd2 +; inx +; bne :- +;: + + ldax #eth_inp + ap_shw + jsr ac_add_source ; add to cache + + lda #arp_idle + sta arp_state + + rts + +;gotmsg: +; .byte "gOT arp REPLY",13,0 +; +;addmsg: +; .byte "aDDING ARP ENTRY",13,0 + + +; add arp_mac and arp_ip to the cache +arp_add: + jsr findip ; check if ip is already in cache + bcs @add + + ldy #9 ; update old entry +: lda arp,y ; move to top as well? + sta (ap),y + dey + bpl :- + rts + +@add: + ldax #arp ; add + + +; add source to cache +ac_add_source: + stax ap + +; ldx #0 +;: lda addmsg,x +; beq :+ +; jsr $ffd2 +; inx +; bne :- +;: + + ldx #9 ; make space in the arp cache +: +; lda arp_cache + 140,x +; sta arp_cache + 150,x +; lda arp_cache + 130,x +; sta arp_cache + 140,x +; lda arp_cache + 120,x +; sta arp_cache + 130,x + +; lda arp_cache + 110,x +; sta arp_cache + 120,x +; lda arp_cache + 100,x +; sta arp_cache + 110,x +; lda arp_cache + 90,x +; sta arp_cache + 100,x +; lda arp_cache + 80,x +; sta arp_cache + 90,x + +; lda arp_cache + 70,x +; sta arp_cache + 80,x + lda arp_cache + 60,x + sta arp_cache + 70,x + lda arp_cache + 50,x + sta arp_cache + 60,x + lda arp_cache + 40,x + sta arp_cache + 50,x + + lda arp_cache + 30,x + sta arp_cache + 40,x + lda arp_cache + 20,x + sta arp_cache + 30,x + lda arp_cache + 10,x + sta arp_cache + 20,x + lda arp_cache,x + sta arp_cache + 10,x + + dex + bpl :- + + ldy #9 +: lda (ap),y ; copy source + sta arp_cache,y + dey + bpl :- + + rts + + +; adds proto = arp, hw = eth, and proto = ip to outgoing packet +makearppacket: + lda #eth_proto_arp + jsr eth_set_proto + + lda #0 ; set hw type (eth = 0001) + sta eth_outp + ap_hw + lda #1 + sta eth_outp + ap_hw + 1 + + lda #8 ; set protcol (ip = 0800) + sta eth_outp + ap_proto + lda #0 + sta eth_outp + ap_proto + 1 + + lda #6 ; set hw addr len (eth = 06) + sta eth_outp + ap_hwlen + lda #4 ; set proto addr len (eth = 04) + sta eth_outp + ap_protolen + + rts diff --git a/client/ip65/config.s b/client/ip65/config.s new file mode 100644 index 0000000..47ad5b4 --- /dev/null +++ b/client/ip65/config.s @@ -0,0 +1,21 @@ +;originally from Per Olofsson's IP65 library - http://www.paradroid.net/ip65 + +; Configuration + + + .export cfg_mac + .export cfg_ip + .export cfg_netmask + .export cfg_gateway + .export cfg_dns + .export cfg_tftp_server + +.data ; these are defaults + +cfg_mac: .byte $00, $80, $10, $6d, $76, $30 +;cfg_ip: .byte 192, 168, 0, 64 +cfg_ip: .byte 0,0,0,0 +cfg_netmask: .byte 255, 255, 255, 0 +cfg_gateway: .byte 0, 0, 0, 0 +cfg_dns: .byte 0, 0, 0, 0 +cfg_tftp_server: .byte $ff,$ff,$ff,$ff diff --git a/client/ip65/copymem.s b/client/ip65/copymem.s new file mode 100644 index 0000000..1f968c6 --- /dev/null +++ b/client/ip65/copymem.s @@ -0,0 +1,54 @@ +;originally from Per Olofsson's IP65 library - http://www.paradroid.net/ip65 + +; copy memory + + + .export copymem + .exportzp copy_src + .exportzp copy_dest + + + .segment "IP65ZP" : zeropage + +; pointers for copying +copy_src: .res 2 ; source pointer +copy_dest: .res 2 ; destination pointer + + + .bss + +end: .res 1 + + + .code + +; copy memory +; set copy_src and copy_dest, length in A/X +copymem: + sta end + ldy #0 + + cpx #0 + beq @tail + +: lda (copy_src),y + sta (copy_dest),y + iny + bne :- + inc copy_src+1 ;next page + inc copy_dest+1 ;next page + dex + bne :- + +@tail: + lda end + beq @done + +: lda (copy_src),y + sta (copy_dest),y + iny + cpy end + bne :- + +@done: + rts diff --git a/client/ip65/cs8900a.i b/client/ip65/cs8900a.i new file mode 100644 index 0000000..0486960 --- /dev/null +++ b/client/ip65/cs8900a.i @@ -0,0 +1,7 @@ +;originally from Per Olofsson's IP65 library - http://www.paradroid.net/ip65 + +pp_rx_ctl = $0104 +pp_line_ctl = $0112 +pp_self_ctl = $0114 +pp_bus_status = $0138 +pp_ia = $0158 diff --git a/client/ip65/cs8900a.s b/client/ip65/cs8900a.s new file mode 100644 index 0000000..f7d5e6b --- /dev/null +++ b/client/ip65/cs8900a.s @@ -0,0 +1,275 @@ +;originally from Per Olofsson's IP65 library - http://www.paradroid.net/ip65 + +; Ethernet driver for CS8900A +; +; Based on Doc Bacardi's tftp source + + +.include "../inc/common.i" +.include "cs8900a.i" + + + ;.import dbg_dump_eth_header + + + .export eth_init + .export eth_rx + .export eth_tx + + .export eth_inp + .export eth_inp_len + .export eth_outp + .export eth_outp_len + + .exportzp eth_dest + .exportzp eth_src + .exportzp eth_type + .exportzp eth_data + + .import cs_init + .import cs_packet_page + .import cs_packet_data + .import cs_rxtx_data + .import cs_tx_cmd + .import cs_tx_len + + .import cfg_mac + + + .macro write_page page, value + lda #page/2 + ldx #value + jsr cs_write_page + .endmacro + + + .segment "IP65ZP" : zeropage + +eth_packet: .res 2 + + + .bss + +; input and output buffers +eth_inp_len: .res 2 ; input packet length +eth_inp: .res 1518 ; space for input packet +eth_outp_len: .res 2 ; output packet length +eth_outp: .res 1518 ; space for output packet + +; ethernet packet offsets +eth_dest = 0 ; destination address +eth_src = 6 ; source address +eth_type = 12 ; packet type +eth_data = 14 ; packet data + + + .code + +; initialize, return clc on success +eth_init: + jsr cs_init + + lda #0 ; check magic signature + jsr cs_read_page + cpx #$0e + bne @notfound + cpy #$63 + bne @notfound + + lda #1 + jsr cs_read_page + cpx #0 + bne @notfound + ; y contains chip rev + + write_page pp_self_ctl, $0055 ; $0114, reset chip + + write_page pp_rx_ctl, $0d05 ; $0104, accept individual and broadcast packets + ;write_page pp_rx_ctl, $0d85 ; $0104, promiscuous mode + + lda #pp_ia/2 ; $0158, write mac address + ldx cfg_mac + ldy cfg_mac + 1 + jsr cs_write_page + + lda #pp_ia/2 + 1 + ldx cfg_mac + 2 + ldy cfg_mac + 3 + jsr cs_write_page + + lda #pp_ia/2 + 2 + ldx cfg_mac + 4 + ldy cfg_mac + 5 + jsr cs_write_page + + write_page pp_line_ctl, $00d3 ; $0112, enable rx and tx + + clc + rts + +@notfound: + sec + rts + + +; receive a packet +eth_rx: + lda #$24 ; check rx status + sta cs_packet_page + lda #$01 + sta cs_packet_page + 1 + + lda cs_packet_data + 1 + and #$0d + bne :+ + + sec ; no packet ready + rts + +: lda cs_rxtx_data + 1 ; ignore status + lda cs_rxtx_data + + lda cs_rxtx_data + 1 ; read packet length + sta eth_inp_len + 1 + tax ; save + lda cs_rxtx_data + sta eth_inp_len + + lda #eth_inp + sta eth_packet + 1 + + ldy #0 + cpx #0 ; < 256 bytes left? + beq @tail + +@get256: + lda cs_rxtx_data + sta (eth_packet),y + iny + lda cs_rxtx_data + 1 + sta (eth_packet),y + iny + bne @get256 + inc eth_packet + 1 + dex + bne @get256 + +@tail: + lda eth_inp_len ; bytes left / 2, round up + lsr + adc #0 + beq @done + tax + +@get: + lda cs_rxtx_data + sta (eth_packet),y + iny + lda cs_rxtx_data + 1 + sta (eth_packet),y + iny + dex + bne @get + +@done: + clc + rts + + +; send a packet +eth_tx: + ;jsr dbg_dump_eth_header + + lda #$c9 ; ask for buffer space + sta cs_tx_cmd + lda #0 + sta cs_tx_cmd + 1 + + lda eth_outp_len ; set length + sta cs_tx_len + lda eth_outp_len + 1 + sta cs_tx_len + 1 + and #$f8 + beq :+ + + inc $d020 + sec ; oversized packet + rts + +: lda #pp_bus_status + sta cs_packet_page + 1 + +@waitspace: + lda cs_packet_data + 1 ; wait for space + ldx cs_packet_data + lsr + bcs @gotspace + jsr @done ; polling too fast doesn't work, delay added by David Schmidt + jmp @waitspace +@gotspace: + ldax #eth_outp ; send packet + stax eth_packet + + ldy #0 + ldx eth_outp_len + 1 + beq @tail + +@send256: + lda (eth_packet),y + sta cs_rxtx_data + iny + lda (eth_packet),y + sta cs_rxtx_data + 1 + iny + bne @send256 + inc eth_packet + 1 + dex + bne @send256 + +@tail: + ldx eth_outp_len + beq @done + +@send: + lda (eth_packet),y + sta cs_rxtx_data + dex + beq @done + iny + lda (eth_packet),y + sta cs_rxtx_data + 1 + iny + dex + bne @send + +@done: ; also used by timeout code above + clc + rts + + +; read X/Y from page A * 2 +cs_read_page: + asl + sta cs_packet_page + lda #0 + rol + sta cs_packet_page + 1 + ldx cs_packet_data + ldy cs_packet_data + 1 + rts + +; write X/Y to page A * 2 +cs_write_page: + asl + sta cs_packet_page + lda #0 + rol + sta cs_packet_page + 1 + stx cs_packet_data + sty cs_packet_data + 1 + rts diff --git a/client/ip65/debug.s b/client/ip65/debug.s new file mode 100644 index 0000000..30efaa8 --- /dev/null +++ b/client/ip65/debug.s @@ -0,0 +1,130 @@ +;originally from Per Olofsson's IP65 library - http://www.paradroid.net/ip65 + +.include "../inc/common.i" +.include "../inc/printf.i" + + + .export dbgout16 + .export dbg_dump_eth_header + .export dbg_dump_ip_header + .export dbg_dump_udp_header + + .export console_out + .export console_strout + + + .import eth_outp, eth_outp_len + .import ip_outp + .import udp_outp + + + .segment "IP65ZP" : zeropage + +cptr: .res 2 + + + .code + + +dbg_dump_eth_header: + pha + txa + pha + tya + pha + + printf "\rethernet header:\r" + printf "len: %04x\r", eth_outp_len + printf "dest: %04x:%04x:%04x\r", eth_outp, eth_outp + 2, eth_outp + 4 + printf "src: %04x:%04x:%04x\r", eth_outp + 6, eth_outp + 8, eth_outp + 10 + printf "type: %04x\r", eth_outp + 12 + + pla + tay + pla + tax + pla + rts + + +dbg_dump_ip_header: + pha + txa + pha + tya + pha + + printf "\rip header:\r" + printf "ver,ihl,tos: %04x\r", ip_outp + printf "len: %04x\r", ip_outp + 2 + printf "id: %04x\r", ip_outp + 4 + printf "frag: %04x\r", ip_outp + 6 + printf "ttl: %02x\r", ip_outp + 8 + printf "proto: %02x\r", ip_outp + 9 + printf "cksum: %04x\r", ip_outp + 10 + printf "src: %04x%04x\r", ip_outp + 12, ip_outp + 14 + printf "dest: %04x%04x\r", ip_outp + 16, ip_outp + 18 + + pla + tay + pla + tax + pla + rts + + +dbg_dump_udp_header: + pha + txa + pha + tya + pha + + printf "\rudp header:\r" + printf "srcport: %04x\r", ip_outp + printf "destport: %04x\r", ip_outp + 2 + printf "len: %04x\r", ip_outp + 4 + printf "cksum: %04x\r", ip_outp + 6 + + pla + tay + pla + tax + pla + rts + + +console_out = $ffd2 + +console_strout: + stax cptr + + pha + txa + pha + tya + pha + ldy #0 +: lda (cptr),y + beq @done + jsr console_out + iny + bne :- +@done: + pla + tay + pla + tax + pla + rts + + +dbgout16: + stax val16 + printf "%04x", val16 + rts + + + .bss + +val16: .res 2 diff --git a/client/ip65/dhcp.s b/client/ip65/dhcp.s new file mode 100644 index 0000000..32b83e7 --- /dev/null +++ b/client/ip65/dhcp.s @@ -0,0 +1,448 @@ +;######################## +; minimal dhcp implementation +; written by jonno@jamtronix.com 2009 +; +;######################## +; to use - first call ip65_init, then call dhcp_init +; no inputs required. +; on return, carry flag clear means IP config has been +; sucesfully obtained (cfg_ip, cfg_netmask, cfg_gateway and cfg_dns set as appropriate). +; if carry flag is set, IP config could not be set. that could be because of a network +; error or because there was no response from a DHCP server within about 5 seconds. +;######################## + + +MAX_DHCP_MESSAGES_SENT=12 ;timeout after sending 12 messages will be about 15 seconds (1+2+3...)/4 + + .include "../inc/common.i" + .export dhcp_init + .export dhcp_server + .export dhcp_state + + .import cfg_mac + .import cfg_ip + .import cfg_netmask + .import cfg_gateway + .import cfg_dns + + .import arp_init + + .import ip65_process + + .import udp_add_listener + .import udp_remove_listener + + .import udp_callback + .import udp_send + + .import udp_inp + + .importzp udp_data + + .import udp_send_dest + .import udp_send_src_port + .import udp_send_dest_port + .import udp_send_len + + .import timer_read + + .bss + +; dhcp packet offsets +dhcp_inp = udp_inp + udp_data +dhcp_outp: .res 576 +dhcp_op = 0 +dhcp_htype = 1 +dhcp_hlen = 2 +dhcp_hops = 3 +dhcp_xid = 4 +dhcp_secs = 8 +dhcp_flags = 10 +dhcp_ciaddr = 12 +dhcp_yiaddr = 16 +dhcp_siaddr = 20 +dhcp_giaddr = 24 +dhcp_chaddr =28 +dhcp_sname = 44 +dhcp_file=108 +dhcp_cookie=236 +dhcp_options=240 + +dhcp_server_port=67 +dhcp_client_port=68 + + +; dhcp state machine +dhcp_initializing = 1 ; initial state +dhcp_selecting = 2 ; sent a DHCPDISCOVER, waiting for a DHCPOFFER +dhcp_ready_to_request = 3 ; got a DHCPOFFER, ready to send a DHCPREQUEST +dhcp_requesting = 4 ; sent a DHCPREQUEST, waiting for a DHCPACK +dhcp_bound = 5 ; we have been allocated an IP address + +dhcp_state: .res 1 ; current activity + +dhcp_message_sent_count: .res 1 +dhcp_timer: .res 1 +dhcp_loop_count: .res 1 +dhcp_break_polling_loop: .res 1 + +dhcp_server: .res 4 + +;DHCP constants +BOOTREQUEST =1 +BOOTREPLY =2 + +DHCPDISCOVER =1 +DHCPOFFER =2 +DHCPREQUEST =3 +DHCPDECLINE =4 +DHCPACK =5 +DHCPNAK =6 +DHCPRELEASE =7 +DHCPINFORM =8 + + + .code + +dhcp_init: + + + ldx #3 ; rewrite ip address + lda #0 +: sta cfg_ip,x + dex + bpl :- + + lda #dhcp_initializing + sta dhcp_state + + ldax #dhcp_in + stax udp_callback + ldax #dhcp_client_port + jsr udp_add_listener + bcc :+ + rts +: + lda #0 ;reset the "message sent" counter + sta dhcp_message_sent_count + jsr send_dhcpdiscover + +@dhcp_polling_loop: + + lda dhcp_message_sent_count + adc #1 + sta dhcp_loop_count ;we wait a bit longer between each resend + +@outer_delay_loop: + lda #0 + sta dhcp_break_polling_loop + jsr timer_read + stx dhcp_timer ;we only care about the high byte + +@inner_delay_loop: + jsr ip65_process + lda #0 + cmp dhcp_break_polling_loop + bne @break_polling_loop + jsr timer_read + cpx dhcp_timer ;this will tick over after about 1/4 of a second + beq @inner_delay_loop + + dec dhcp_loop_count + bne @outer_delay_loop + +@break_polling_loop: + + inc dhcp_message_sent_count + lda dhcp_message_sent_count + cmp #MAX_DHCP_MESSAGES_SENT-1 + bpl @too_many_messages_sent + lda dhcp_state + cmp #dhcp_initializing + beq @initializing + cmp #dhcp_selecting + beq @selecting + cmp #dhcp_ready_to_request + beq @ready_to_request + cmp #dhcp_bound + beq @bound + jmp @dhcp_polling_loop +@initializing: +@selecting: + jsr send_dhcpdiscover + jmp @dhcp_polling_loop + +@ready_to_request: + jsr send_dhcprequest + jmp @dhcp_polling_loop + +@bound: + ldax #dhcp_client_port + jsr udp_remove_listener + rts + +@too_many_messages_sent: + sec ;signal an error + rts + +dhcp_create_request_msg: + lda #BOOTREQUEST + sta dhcp_outp+dhcp_op + lda #1 ;htype 1 = "10 MB ethernet" + sta dhcp_outp+dhcp_htype + lda #6 ;ethernet MACs are 6 bytes + sta dhcp_outp+dhcp_hlen + lda #0 ;hops = 0 + sta dhcp_outp+dhcp_hops + ldx #3 ;set xid to be "1234" + clc +: txa + adc #01 + sta dhcp_outp+dhcp_xid,x + dex + bpl :- + + lda #0 ;secs =00 + sta dhcp_outp+dhcp_secs + sta dhcp_outp+dhcp_secs+1 + ;initially turn off all flags + sta dhcp_outp+dhcp_flags + sta dhcp_outp+dhcp_flags+1 + + ldx #$0F ;set ciaddr to 0.0.0.0 + ;set yiaddr to 0.0.0.0 + ;set siaddr to 0.0.0.0 + ;set giaddr to 0.0.0.0 +: sta dhcp_outp+dhcp_ciaddr,x + dex + bpl :- + + ldx #5 ;set chaddr to mac +: lda cfg_mac,x + sta dhcp_outp+dhcp_chaddr,x + dex + bpl :- + + ldx #192 ;set sname & file both to null + lda #0 +: sta dhcp_outp+dhcp_sname-1,x + dex + bne :- + + + lda #$63 ;copy the magic cookie + sta dhcp_outp+dhcp_cookie+0 + lda #$82 + sta dhcp_outp+dhcp_cookie+1 + lda #$53 + sta dhcp_outp+dhcp_cookie+2 + lda #$63 + sta dhcp_outp+dhcp_cookie+3 + + ldax #dhcp_client_port ; set source port + stax udp_send_src_port + + ldax #dhcp_server_port ; set destination port + stax udp_send_dest_port + + rts + +send_dhcpdiscover: + lda #dhcp_initializing + sta dhcp_state + + jsr dhcp_create_request_msg + + lda #$80 ;broadcast flag =1, all other bits 0 + sta dhcp_outp+dhcp_flags + + lda #53 ;option 53 - DHCP message type + sta dhcp_outp+dhcp_options+0 + lda #1 ;option length is 1 + sta dhcp_outp+dhcp_options+1 + lda #DHCPDISCOVER + sta dhcp_outp+dhcp_options+2 + lda #$FF ;option FF = end of options + sta dhcp_outp+dhcp_options+3 + + ldx #3 ; set destination address + lda #$FF ; des = 255.255.255.255 (broadcast) +: sta udp_send_dest,x + dex + bpl :- + + ldax #dhcp_options+4 + stax udp_send_len + ldax #dhcp_outp + jsr udp_send + bcc :+ + rts + +: lda #dhcp_selecting + sta dhcp_state + rts + +;got a message on port 68 +dhcp_in: + + lda dhcp_inp+dhcp_op + cmp #BOOTREPLY + beq :+ + rts ;it's not what we were expecting +: + + lda #0 + cmp dhcp_inp+dhcp_yiaddr ;is the first byte in the assigned address 0? + bne :+ + rts ;if so, it's a bogus response - ignore +: + ldx #4 ;copy the our new IP address +: + lda dhcp_inp+dhcp_yiaddr,x + sta cfg_ip,x + dex + bpl :- + + ldx #0 +@unpack_dhcp_options: + lda dhcp_inp+dhcp_options,x + cmp #$ff + bne :+ + jmp @finished_unpacking_dhcp_options +: + cmp #53 ;is this field DHCP message type? + bne @not_dhcp_message_type + jmp @get_next_option + lda dhcp_inp+dhcp_options+2,x + cmp #DHCPOFFER ;if it's not a DHCP OFFER message, then stop processing + beq :+ + rts +: jmp @get_next_option + +@not_dhcp_message_type: + + cmp #1 ;option 1 is netmask + bne @not_netmask + lda dhcp_inp+dhcp_options+2,x + sta cfg_netmask + lda dhcp_inp+dhcp_options+3,x + sta cfg_netmask+1 + lda dhcp_inp+dhcp_options+4,x + sta cfg_netmask+2 + lda dhcp_inp+dhcp_options+5,x + sta cfg_netmask+3 + jmp @get_next_option + +@not_netmask: + + cmp #3 ;option 3 is gateway + bne @not_gateway + lda dhcp_inp+dhcp_options+2,x + sta cfg_gateway + lda dhcp_inp+dhcp_options+3,x + sta cfg_gateway+1 + lda dhcp_inp+dhcp_options+4,x + sta cfg_gateway+2 + lda dhcp_inp+dhcp_options+5,x + sta cfg_gateway+3 + jmp @get_next_option + +@not_gateway: + + cmp #6 ;option 3 6 is dns server + bne @not_dns_server + lda dhcp_inp+dhcp_options+2,x + sta cfg_dns + lda dhcp_inp+dhcp_options+3,x + sta cfg_dns+1 + lda dhcp_inp+dhcp_options+4,x + sta cfg_dns+2 + lda dhcp_inp+dhcp_options+5,x + sta cfg_dns+3 + jmp @get_next_option + +@not_dns_server: + + cmp #54 ;option 54 is DHCP server + bne @not_server + lda dhcp_inp+dhcp_options+2,x + sta dhcp_server + lda dhcp_inp+dhcp_options+3,x + sta dhcp_server+1 + lda dhcp_inp+dhcp_options+4,x + sta dhcp_server+2 + lda dhcp_inp+dhcp_options+5,x + sta dhcp_server+3 + jmp @get_next_option + +@not_server: + + +@get_next_option: + txa + clc + adc #02 + adc dhcp_inp+dhcp_options+1,x + bcs @finished_unpacking_dhcp_options ; if we overflow, then we're done + tax + jmp @unpack_dhcp_options + + +@finished_unpacking_dhcp_options: + jsr arp_init ;we have modified our netmask, so we need to recalculate gw_test + lda dhcp_state + cmp #dhcp_bound + beq :+ + lda #dhcp_ready_to_request + sta dhcp_state +: + lda #1 + sta dhcp_break_polling_loop + + rts + +send_dhcprequest: + jsr dhcp_create_request_msg + lda #53 ;option 53 - DHCP message type + sta dhcp_outp+dhcp_options+0 + lda #1 ;option length is 1 + sta dhcp_outp+dhcp_options+1 + lda #DHCPREQUEST + sta dhcp_outp+dhcp_options+2 + + lda #50 ;option 50 - requested IP address + sta dhcp_outp+dhcp_options+3 + ldx #4 ;option length is 4 + stx dhcp_outp+dhcp_options+4 + dex +: lda cfg_ip,x + sta dhcp_outp+dhcp_options+5,x + dex + bpl :- + + lda #54 ;option 54 - DHCP server + sta dhcp_outp+dhcp_options+9 + ldx #4 ;option length is 4 + stx dhcp_outp+dhcp_options+10 + dex + +: lda dhcp_server,x + sta dhcp_outp+dhcp_options+11,x + sta udp_send_dest,x + dex + bpl :- + + + lda #$FF ;option FF = end of options + sta dhcp_outp+dhcp_options+17 + + ldax #dhcp_options+18 + stax udp_send_len + + ldax #dhcp_outp + jsr udp_send + bcs :+ ;if we didn't send the message we probably need to wait for an ARP reply to come back. + lda #dhcp_bound ;technically, we should wait till we get a DHCPACK message. but we'll assume success + sta dhcp_state +: + rts diff --git a/client/ip65/dns.s b/client/ip65/dns.s new file mode 100644 index 0000000..bf14e26 --- /dev/null +++ b/client/ip65/dns.s @@ -0,0 +1,414 @@ +;######################## +; minimal dns implementation +; requires a DNS server that supports recursion +; written by jonno@jamtronix.com 2009 +; +;######################## +; to use - +; ensure cfg_dns points to a DNS server that supports recursion +; call dns_set_hostname with AX pointing to null terminated hostname +; then call dns_resolve +; on exit: carry flag is set if there was an error. if carry flag is clear, then dns_ip will point to the +; IP address of the hostname +;######################## + + + MAX_DNS_MESSAGES_SENT=8 ;timeout after sending 8 messages will be about 7 seconds (1+2+3+4+5+6+7+8)/4 + +.include "../inc/common.i" + + .export dns_set_hostname + .export dns_resolve + .export dns_ip + .export dns_status + + .import cfg_dns + + .import ip65_process + + .import udp_add_listener + .import udp_remove_listener + + .import udp_callback + .import udp_send + + .import udp_inp + + .importzp udp_data + + .import udp_send_dest + .import udp_send_src_port + .import udp_send_dest_port + .import udp_send_len + + .import timer_read + + .segment "IP65ZP" : zeropage + + dns_hostname: .res 2 + + .bss + +; dns packet offsets +dns_inp = udp_inp + udp_data +dns_outp: .res 256 +dns_id = 0 +dns_flags=2 +dns_qdcount=4 +dns_ancount=6 +dns_nscount=8 +dns_arcount=10 +dns_qname=12 + +dns_server_port=53 +dns_client_port_low_byte: .res 1 + +dns_ip: .res 4 + +dns_msg_id: .res 2 + +dns_current_label_length: .res 1 +dns_current_label_offset: .res 1 + +dns_message_sent_count: .res 1 + +dns_packed_hostname: .res 128 + +; dns state machine +dns_initializing = 1 ; initial state +dns_query_sent = 2 ; sent a query, waiting for a response +dns_complete = 3 ; got a good response +dns_failed = 4 ; got either a 'no such name' or 'recursion declined' response + +dns_state: .res 1 ; current activity +dns_timer: .res 1 +dns_loop_count: .res 1 +dns_break_polling_loop: .res 1 + +dns_status: .res 2 + +hostname_copied: .res 1 + +questions_in_response: .res 1 + + + .code + +dns_set_hostname: + stax dns_hostname + ;copy the hostname into a buffer suitable to copy directly into the qname field + ;we need to split on dots + ldy #0 ;input pointer + ldx #1 ;output pointer (start at 1, to skip first length offset, which will be filled in later) + + sty dns_current_label_length + sty dns_current_label_offset + sty hostname_copied + +@next_hostname_byte: + lda (dns_hostname),y ;get next char in hostname + cmp #0 ;are we at the end of the string? + bne :+ + inc hostname_copied + bne @set_length_of_last_label +: + + cmp #'.' ;do we need to split the labels? + bne @not_a_dot +@set_length_of_last_label: + txa + pha + lda dns_current_label_length + ldx dns_current_label_offset + sta dns_packed_hostname,x + lda #0 + sta dns_current_label_length + pla + tax + stx dns_current_label_offset + lda hostname_copied + beq @update_counters + jmp @hostname_done +@not_a_dot: + sta dns_packed_hostname,x + inc dns_current_label_length + +@update_counters: + iny + inx + bmi @hostname_too_long ;don't allow a hostname of more than 128 bytes + jmp @next_hostname_byte + +@hostname_done: + + lda dns_packed_hostname-1,x ;get the last byte we wrote out + beq :+ ;was it a zero? + lda #0 + sta dns_packed_hostname,x ;write a trailing zero (i.e. a zero length label) + inx +: + clc ;no error + + rts + +@hostname_too_long: + sec + rts + +dns_resolve: + ldax #dns_in + stax udp_callback + lda #53 + inc dns_client_port_low_byte ;each call to resolve uses a different client address + ldx dns_client_port_low_byte ;so we don't get confused by late replies to a previous call + jsr udp_add_listener + + bcc :+ + rts +: + + lda #dns_initializing + sta dns_state + lda #0 ;reset the "message sent" counter + sta dns_message_sent_count + + jsr send_dns_query + +@dns_polling_loop: + lda dns_message_sent_count + adc #1 + sta dns_loop_count ;we wait a bit longer between each resend +@outer_delay_loop: + lda #0 + sta dns_break_polling_loop + jsr timer_read + stx dns_timer ;we only care about the high byte + +@inner_delay_loop: + jsr ip65_process + lda dns_state + cmp #dns_complete + beq @complete + cmp #dns_failed + beq @failed + + lda #0 + cmp dns_break_polling_loop + bne @break_polling_loop + jsr timer_read + cpx dns_timer ;this will tick over after about 1/4 of a second + beq @inner_delay_loop + + dec dns_loop_count + bne @outer_delay_loop + +@break_polling_loop: + jsr send_dns_query + inc dns_message_sent_count + lda dns_message_sent_count + cmp #MAX_DNS_MESSAGES_SENT-1 + bpl @too_many_messages_sent + jmp @dns_polling_loop + +@complete: + lda #53 + ldx dns_client_port_low_byte + jsr udp_remove_listener + rts + +@too_many_messages_sent: +@failed: + lda #53 + ldx dns_client_port_low_byte + jsr udp_remove_listener + sec ;signal an error + rts + +send_dns_query: + + ldax dns_msg_id + inx + adc #0 + stax dns_msg_id + stax dns_outp+dns_id + + ldax #$0001 ;QR =0 (query), opcode=0 (query), AA=0, TC=0,RD=1,RA=0,Z=0,RCODE=0 + stax dns_outp+dns_flags + ldax #$0100 ;we ask 1 question + stax dns_outp+dns_qdcount + ldax #$0000 + stax dns_outp+dns_ancount ;we send no answers + stax dns_outp+dns_nscount ;we send no name servers + stax dns_outp+dns_arcount ;we send no authorative records + + ldx #0 +: + lda dns_packed_hostname,x + sta dns_outp+dns_qname,x + inx + bmi @error_on_send ;if we got past 128 bytes, there's a problem + cmp #0 + bne :- ;keep looping until we have a zero byte. + + lda #0 + sta dns_outp+dns_qname,x ;high byte of QTYPE=1 (A) + sta dns_outp+dns_qname+2,x ;high byte of QLASS=1 (IN) + lda #1 + sta dns_outp+dns_qname+1,x ;low byte of QTYPE=1 (A) + sta dns_outp+dns_qname+3,x ;low byte of QLASS=1 (IN) + + txa + clc + adc #(dns_qname+4) + ldx #0 + stax udp_send_len + + lda #53 + ldx dns_client_port_low_byte + stax udp_send_src_port + + ldx #3 ; set destination address +: lda cfg_dns,x + sta udp_send_dest,x + dex + bpl :- + + ldax #dns_server_port ; set destination port + stax udp_send_dest_port + ldax #dns_outp + jsr udp_send + bcs @error_on_send + lda #dns_query_sent + sta dns_state + rts +@error_on_send: + + sec + rts + +dns_in: + lda dns_inp+dns_flags+1 ; + and #$0f ;get the RCODE + cmp #0 + beq @not_an_error_response + + sta dns_status ;anything non-zero is a permanent error (invalid domain, server doesn't support recursion etc) + sta dns_status+1 + lda #dns_failed + sta dns_state + rts +@not_an_error_response: + lda dns_inp+dns_qdcount+1 + sta questions_in_response + cmp #1 ;should be exactly 1 Q in the response (i.e. the one we sent) + beq :+ + jmp @error_in_response +: + lda dns_inp+dns_ancount+1 + bne :+ + jmp @error_in_response ;should be at least 1 answer in response +: ;we need to skip over the question (we will assume it's the question we just asked) + ldx #dns_qname +: + lda dns_inp,x ;get next length byte in question + beq :+ ; we're done if length==0 + clc + txa + + adc dns_inp,x ;add length of next label to ptr + adc #1 ;+1 for the length byte itself + tax + bcs @error_in_response ;if we overflowed x, then message is too big + bcc :- +: + inx ;skip past the nul byte + lda dns_inp+1,x + cmp #1 ;QTYPE should 1 + lda dns_inp+3,x + cmp #1 ;QCLASS should 1 + bne @error_in_response + + inx ;skip past the QTYPE/QCLASS + inx + inx + inx + + ;x now points to the start of the answers + + lda dns_inp,x + bpl @error_in_response ;we are expecting the high bit to be set (we assume the server will send us back the answer to the question we just asked) + inx ;skip past the compression + inx + ;we are now pointing at the TYPE field + lda dns_inp+1,x ; + + cmp #5 ; is this a CNAME? + bne @not_a_cname + + + txa + clc + adc #10 ;skip 2 bytes TYPE, 2 bytes CLASS, 4 bytes TTL, 2 bytes RDLENGTH + tax + ;we're now pointing at the CNAME record + ldy #0 ;start of CNAME hostname +: + lda dns_inp,x + beq @last_byte_of_cname + bmi @found_compression_marker + sta dns_packed_hostname,y + inx + iny + bmi @error_in_response ;if we go past 128 bytes, something is wrong + bpl :- ;go get next byte + @last_byte_of_cname: + sta dns_packed_hostname,y + + lda #$ff ;set a status marker so we know whats going on + sta dns_status + stx dns_status+1 + + lda #1 + sta dns_break_polling_loop + + rts ; finished processing - the main dns polling loop should now resend a query, this time for the hostname from the CNAME record + +@found_compression_marker: + lda dns_inp+1,x + tax + jmp :- + +@not_a_cname: + cmp #1 ; should be 1 (A record) + bne @error_in_response + txa + clc + adc #10 ;skip 2 bytes TYPE, 2 bytes CLASS, 4 bytes TTL, 2 bytes RDLENGTH + tax + ;we're now pointing at the answer! + lda dns_inp,x + sta dns_ip + + lda dns_inp+1,x + sta dns_ip+1 + + lda dns_inp+2,x + sta dns_ip+2 + + lda dns_inp+3,x + sta dns_ip+3 + + + lda #dns_complete + sta dns_state + + lda #1 + sta dns_break_polling_loop + +@error_in_response: + + sta dns_status + stx dns_status+1 + rts + + diff --git a/client/ip65/eth.s b/client/ip65/eth.s new file mode 100644 index 0000000..61ad8de --- /dev/null +++ b/client/ip65/eth.s @@ -0,0 +1,55 @@ +;originally from Per Olofsson's IP65 library - http://www.paradroid.net/ip65 + +; Common ethernet driver code + + +.include "../inc/common.i" + + .export eth_set_broadcast_dest + .export eth_set_my_mac_src + .export eth_set_proto + + .exportzp eth_proto_ip + .exportzp eth_proto_arp + + .import eth_outp + + .import cfg_mac + + +; ethernet packet offsets +eth_dest = 0 ; destination address +eth_src = 6 ; source address +eth_type = 12 ; packet type +eth_data = 14 ; packet data + +; protocols +eth_proto_ip = 0 +eth_proto_arp = 6 + + + .code + +eth_set_broadcast_dest: + ldx #5 + lda #$ff +: sta eth_outp,x + dex + bpl :- + rts + + +eth_set_my_mac_src: + ldx #5 +: lda cfg_mac,x + sta eth_outp + 6,x + dex + bpl :- + rts + + +eth_set_proto: + sta eth_outp + eth_type + 1 + lda #8 + sta eth_outp + eth_type + rts diff --git a/client/ip65/icmp.s b/client/ip65/icmp.s new file mode 100644 index 0000000..2e1b432 --- /dev/null +++ b/client/ip65/icmp.s @@ -0,0 +1,226 @@ +;originally from Per Olofsson's IP65 library - http://www.paradroid.net/ip65 + +.include "../inc/common.i" + + .export icmp_init + .export icmp_process + .export icmp_add_listener + .export icmp_remove_listener + + .export icmp_callback + + .export icmp_inp + .export icmp_outp + .exportzp icmp_type + .exportzp icmp_code + .exportzp icmp_cksum + .exportzp icmp_data + + + .import ip_calc_cksum + .import ip_inp + .import ip_outp + .import ip_broadcast + .importzp ip_cksum_ptr + .importzp ip_header_cksum + .importzp ip_src + .importzp ip_dest + .importzp ip_data + + .import eth_tx + .import eth_inp + .import eth_inp_len + .import eth_outp + .import eth_outp_len + + + .bss + +; argument for icmp_add_listener +icmp_callback: .res 2 + +; icmp callbacks +icmp_cbmax = 4 +icmp_cbtmp: .res 3 ; temporary vector +icmp_cbveclo: .res icmp_cbmax ; table of listener vectors (lsb) +icmp_cbvechi: .res icmp_cbmax ; table of listener vectors (msb) +icmp_cbtype: .res icmp_cbmax ; table of listener types +icmp_cbcount: .res 1 ; number of active listeners + +; icmp packet offsets +icmp_inp = ip_inp + ip_data +icmp_outp = ip_outp + ip_data +icmp_type = 0 +icmp_code = 1 +icmp_cksum = 2 +icmp_data = 4 + +; icmp echo packet offsets +icmp_echo_id = 4 +icmp_echo_seq = 6 +icmp_echo_data = 8 + + + .code + +; initialize icmp +icmp_init: + lda #0 + sta icmp_cbcount + lda #$4c ; jmp addr + sta icmp_cbtmp + rts + + +; process incoming icmp packet +icmp_process: + lda icmp_inp + icmp_type + cmp #8 ; ping + beq @echo + + lda icmp_cbcount ; any installed icmp listeners? + beq @drop + + ldx icmp_cbcount ; check listened types + dex +: lda icmp_cbtype,x + cmp icmp_inp + icmp_type + beq @handle ; found a match + dex + bpl :- + +@drop: + sec + rts + +@handle: + lda icmp_cbveclo,x ; copy vector + sta icmp_cbtmp + 1 + lda icmp_cbvechi,x + sta icmp_cbtmp + 2 + jsr icmp_cbtmp ; call listener + clc + rts + +@echo: + lda ip_broadcast ; check if packet is broadcast + beq @notbc + sec ; don't reply to broadcast pings + rts +@notbc: + ldx #5 +: lda eth_inp,x ; swap dest and src mac + sta eth_outp + 6,x + lda eth_inp + 6,x + sta eth_outp,x + dex + bpl :- + + ldx #12 ; copy the packet +: lda eth_inp,x + sta eth_outp,x + inx + cpx eth_inp_len + bne :- + + ldx #3 +: lda ip_inp + ip_src,x ; swap dest and src ip + sta ip_outp + ip_dest,x + lda ip_inp + ip_dest,x + sta ip_outp + ip_src,x + dex + bpl :- + + lda #0 ; change type to reply + sta icmp_outp + icmp_type + + lda icmp_inp + icmp_cksum ; recalc checksum + clc + adc #8 + sta icmp_outp + icmp_cksum + bcc :+ + inc icmp_outp + icmp_cksum + 1 +: + lda eth_inp_len ; copy length + sta eth_outp_len + lda eth_inp_len + 1 + sta eth_outp_len + 1 + + lda #0 ; clear checksum + sta ip_outp + ip_header_cksum + sta ip_outp + ip_header_cksum + 1 + ldax #ip_outp ; calculate ip header checksum + stax ip_cksum_ptr + ldax #20 + jsr ip_calc_cksum + stax ip_outp + ip_header_cksum + + jsr eth_tx ; send packet + + clc + rts + + +; add an icmp listener +; icmp type in A, vector in icmp_callback +icmp_add_listener: + ldx icmp_cbcount ; any listeners at all? + beq @add + cpx #icmp_cbmax ; max? + beq @full + ldx #0 +: cmp icmp_cbtype,x ; check if type is already listened + beq @busy + inx + cpx icmp_cbcount + bne :- +@add: + inc icmp_cbcount ; increase counter + sta icmp_cbtype,x ; add type + lda icmp_callback ; and vector + sta icmp_cbveclo,x + lda icmp_callback + 1 + sta icmp_cbvechi,x + + clc + rts +@full: +@busy: + sec + rts + + +; remove an icmp listener +; icmp type in A +icmp_remove_listener: + ldx icmp_cbcount ; any listeners installed? + beq @notfound +: cmp icmp_cbtype,x ; check if type is listened + beq @remove + inx + cpx icmp_cbcount + bne :- +@notfound: + sec + rts +@remove: + txa ; number of listeners below + eor #$ff + clc + adc icmp_cbcount + beq @done +@move: + tay ; number of items to move +: lda icmp_cbtype + 1,x ; move type + sta icmp_cbtype,x + lda icmp_cbveclo + 1,x ; move vector lsb + sta icmp_cbveclo,x + lda icmp_cbvechi + 1,x ; move vector msb + sta icmp_cbvechi,x + inx + dey + bne :- +@done: + dec icmp_cbcount ; decrement counter + clc + rts diff --git a/client/ip65/ip.s b/client/ip65/ip.s new file mode 100644 index 0000000..5a09dfb --- /dev/null +++ b/client/ip65/ip.s @@ -0,0 +1,410 @@ +;originally from Per Olofsson's IP65 library - http://www.paradroid.net/ip65 + +.include "../inc/common.i" + + ;.import dbg_dump_ip_header + + + .export ip_init + .export ip_process + .export ip_calc_cksum + .export ip_create_packet + .export ip_send + + .export ip_inp + .export ip_outp + .export ip_broadcast + .exportzp ip_cksum_ptr + .exportzp ip_ver_ihl + .exportzp ip_tos + .exportzp ip_len + .exportzp ip_id + .exportzp ip_frag + .exportzp ip_ttl + .exportzp ip_proto + .exportzp ip_header_cksum + .exportzp ip_src + .exportzp ip_dest + .exportzp ip_data + + .exportzp ip_proto_icmp + .exportzp ip_proto_tcp + .exportzp ip_proto_udp + + + .import cfg_mac + .import cfg_ip + + .import eth_tx + .import eth_set_proto + .import eth_inp + .import eth_inp_len + .import eth_outp + .import eth_outp_len + + .importzp eth_dest + .importzp eth_src + .importzp eth_type + .importzp eth_data + .importzp eth_proto_ip + .importzp eth_proto_arp + + .import arp_lookup + .import arp_mac + .import arp_ip + + .import icmp_init + .import icmp_process + + .import udp_init + .import udp_process + + .importzp copy_src + + + .segment "IP65ZP" : zeropage + +; checksum +ip_cksum_ptr: .res 2 ; data pointer + + + .bss + +ip_cksum_len: .res 2 ; length of data + +; ip packets start at ethernet packet + 14 +ip_inp = eth_inp + eth_data +ip_outp = eth_outp + eth_data + +; temp storage for size calculation +len: .res 2 + +; flag for incoming broadcast packets +ip_broadcast: .res 1 + +; ip packet offsets +ip_ver_ihl = 0 +ip_tos = 1 +ip_len = 2 +ip_id = 4 +ip_frag = 6 +ip_ttl = 8 +ip_proto = 9 +ip_header_cksum = 10 +ip_src = 12 +ip_dest = 16 +ip_data = 20 + +; ip protocols +ip_proto_icmp = 1 +ip_proto_tcp = 6 +ip_proto_udp = 17 + + +; temp for calculating checksum +cksum: .res 3 + +; bad packet counters +bad_header: .res 2 +bad_addr: .res 2 + + + .code + +; initialize ip routines +ip_init: + lda #0 + sta bad_header + sta bad_header + 1 + sta bad_addr + sta bad_addr + 1 + + jsr icmp_init + jsr udp_init +; jsr tcp_init + + rts + + +; process an incoming packet +ip_process: + jsr verifyheader ; ver, ihl, len, frag, checksum + bcc @ok +@badpacket: + sec + rts +@ok: + jsr checkaddr ; make sure it's meant for us + bcs @badpacket + + lda ip_inp + ip_proto + cmp #ip_proto_icmp + bne :+ + jmp icmp_process ; jump to icmp handler +: cmp #ip_proto_tcp + bne :+ + jmp tcp_process ; jump to tcp handler +: cmp #ip_proto_udp + bne :+ + jmp udp_process ; jump to udp handler +: +tcp_process: + sec ; unknown protocol + rts + + +; verify that header contains what we expect +verifyheader: + lda ip_inp + ip_ver_ihl ; IPv4 and no IP options + cmp #$45 + bne @badpacket + +; lda ip_inp + ip_tos ; ignore ToS + + lda ip_inp + ip_len + 1 ; ip + 14 bytes ethernet header + clc + adc #14 + sta len + lda ip_inp + ip_len + adc #0 + sta len + 1 + + lda eth_inp_len ; check if advertised length is shorter + sec ; than actual length + sbc len + lda eth_inp_len + 1 + sbc len + 1 + bmi @badpacket + + lda ip_inp + ip_frag ; check for fragmentation + beq :+ + cmp #$40 + bne @badpacket +: lda ip_inp + ip_frag + 1 + bne @badpacket + + ldax #ip_inp ; verify checksum + stax ip_cksum_ptr + ldax #20 + jsr ip_calc_cksum + cmp #0 + bne @badpacket + cpx #0 + bne @badpacket + + clc + rts +@badpacket: + inc bad_header + bne :+ + inc bad_header + 1 +: sec + rts + + +; check that this packet was addressed to us +checkaddr: + lda #0 + sta ip_broadcast + lda ip_inp + ip_dest ; compare ip address + cmp cfg_ip + bne @broadcast + lda ip_inp + ip_dest + 1 + cmp cfg_ip + 1 + bne @broadcast + lda ip_inp + ip_dest + 2 + cmp cfg_ip + 2 + bne @broadcast + lda ip_inp + ip_dest + 3 + cmp cfg_ip + 3 + bne @broadcast +@ok: clc + rts +@broadcast: + inc ip_broadcast + lda ip_inp + ip_dest ; check for broadcast + and ip_inp + ip_dest + 1 + and ip_inp + ip_dest + 2 + and ip_inp + ip_dest + 3 + cmp #$ff + beq @ok + inc bad_addr + bne :+ + inc bad_addr + 1 +: sec + rts + + +; create a packet template +ip_create_packet: + lda #$45 ; set IP version and header length + sta ip_outp + ip_ver_ihl + + lda #0 ; set type of service + sta ip_outp + ip_tos + + ; skip length + + ; skip ID + + lda #$40 ; don't fragment - or should we not care? + sta ip_outp + ip_frag + lda #0 + sta ip_outp + ip_frag + 1 + + lda #$40 ; set time to live + sta ip_outp + ip_ttl + + ; skip protocol + + lda #0 ; clear checksum + sta ip_outp + ip_header_cksum + sta ip_outp + ip_header_cksum + 1 + + ldx #3 ; copy source address +: lda cfg_ip,x + sta ip_outp + ip_src,x + dex + bpl :- + + ; skip destination address + + rts + + +; send an IP packet +; +; but first: +; +; call ip_create_packet +; set length +; set ID +; set protocol +; set destination address +ip_send: + ldx #3 ; get mac addr from ip +: lda ip_outp + ip_dest,x + sta arp_ip,x + dex + bpl :- + + jsr arp_lookup + bcc :+ + rts ; packet buffer nuked, fail +: + ldax #ip_outp ; calculate ip header checksum + stax ip_cksum_ptr + ldax #20 + jsr ip_calc_cksum + stax ip_outp + ip_header_cksum + + ldx #5 +: lda arp_mac,x ; copy destination mac address + sta eth_outp + eth_dest,x + lda cfg_mac,x ; copy my mac address + sta eth_outp + eth_src,x + dex + bpl :- + + lda #eth_proto_ip ; set type to IP + jsr eth_set_proto + + lda ip_outp + ip_len + 1 ; set packet length + lsr + bcc @dontpad + + rol ; pad with 0 + ;clc + adc #ip_outp + sta copy_src + 1 + ldy #0 + tya + sta (copy_src),y + + sec ; round up to even number +@dontpad: + lda ip_outp + ip_len + 1 + adc #eth_data + sta eth_outp_len + lda ip_outp + ip_len + adc #0 + sta eth_outp_len + 1 + + ;jsr dbg_dump_ip_header + + jmp eth_tx ; send packet and return status + + +; calculate checksum for ip header +ip_calc_cksum: + sta ip_cksum_len ; save length + stx ip_cksum_len + 1 + + lda #0 + sta cksum + sta cksum + 1 + sta cksum + 2 + + cpx #0 + beq @tail + +: ldx #$80 ; number of bytes / 2 + jsr @calc + inc ip_cksum_ptr + 1 + dec ip_cksum_len + 1 + bne :- + +@tail: + lda ip_cksum_len ; divide length by 2 + lsr + php ; save carry for odd size + tax + + jsr @calc + + plp + bcc @done + + clc + lda (ip_cksum_ptr),y + adc cksum + sta cksum + bcc @done + inc cksum + 1 + bne @done + inc cksum + 2 + +@done: + lda cksum + 2 ; add carries back in + clc + adc cksum + pha + lda cksum + 1 + adc #0 + eor #$ff ; return inverted result + tax + pla + eor #$ff + + rts + +@calc: + ldy #0 ; 1's complement 16-bit sum +@next: + clc + lda (ip_cksum_ptr),y + adc cksum + sta cksum + iny + lda (ip_cksum_ptr),y + adc cksum + 1 + sta cksum + 1 + bcc :+ + inc cksum + 2 +: iny + dex + bne @next + rts diff --git a/client/ip65/ip65.s b/client/ip65/ip65.s new file mode 100644 index 0000000..9289f42 --- /dev/null +++ b/client/ip65/ip65.s @@ -0,0 +1,76 @@ +;originally from Per Olofsson's IP65 library - http://www.paradroid.net/ip65 + +; ip65 main routines + +.include "../inc/common.i" + + .export ip65_init + .export ip65_process + + .export ip65_ctr + .export ip65_ctr_arp + .export ip65_ctr_ip + + .import eth_init + .import timer_init + .import arp_init + .import ip_init + + .import eth_inp + .import eth_rx + + .import ip_process + .import arp_process + + .importzp eth_proto_arp + + + .bss + +ip65_ctr: .res 1 ; incremented for every incoming packet +ip65_ctr_arp: .res 1 ; incremented for every incoming arp packet +ip65_ctr_ip: .res 1 ; incremented for every incoming ip packet + + + .code + +; initialize stack +ip65_init: + + + jsr eth_init ; initialize ethernet driver + + bcs @fail + jsr timer_init ; initialize timer + jsr arp_init ; initialize arp + jsr ip_init ; initialize ip, icmp, udp, and tcp + clc +@fail: + rts + + +; maintenance routine +; polls for packets, and dispatches to listeners +ip65_process: + jsr eth_rx ; check for incoming packets + bcs @done + + lda eth_inp + 12 ; type should be 08xx + cmp #8 + bne @done + + lda eth_inp + 13 +; cmp #eth_proto_ip ; ip = 00 + beq @ip + cmp #eth_proto_arp ; arp = 06 + beq @arp +@done: + rts + +@arp: + inc ip65_ctr_arp + jmp arp_process + +@ip: + inc ip65_ctr_ip + jmp ip_process diff --git a/client/ip65/printf.s b/client/ip65/printf.s new file mode 100644 index 0000000..01d9609 --- /dev/null +++ b/client/ip65/printf.s @@ -0,0 +1,396 @@ +;originally from Per Olofsson's IP65 library - http://www.paradroid.net/ip65 + +.include "../inc/common.i" + + .export console_printf + + + .import console_out + .import console_strout + + + .segment "IP65ZP" : zeropage + +strptr: .res 2 +argptr: .res 2 +valptr: .res 2 + + + .bss + +ysave: .res 1 +arg: .res 1 +fieldwidth: .res 1 +fieldwcnt: .res 1 +leadzero: .res 1 +argtemp: .res 1 +int: .res 2 +num: .res 5 +ext: .res 2 + + + .code + +console_printf: + stax argptr + ldy #0 + lda (argptr),y + sta strptr + iny + lda (argptr),y + sta strptr + 1 + iny + sty arg + + ldy #0 +@nextchar: + lda (strptr),y + bne :+ + rts +: + cmp #'%' + beq @printarg + + cmp #'\' + beq @printescape + + jsr console_out + +@next: + iny + bne @nextchar + + inc strptr + 1 + jmp @nextchar + +@printescape: + iny + bne :+ + inc strptr + 1 +: lda (strptr),y + ldx #esc_count - 1 +: cmp esc_code,x + beq @escmatch + dex + bpl :- + bmi @next +@escmatch: + lda esc_char,x + jsr console_out + jmp @next + +@printarg: + lda #0 + sta fieldwidth + sta leadzero + lda #$ff + sta fieldwcnt +@argnext: + iny + bne :+ + inc strptr + 1 +: + tya + pha + + lda (strptr),y + + cmp #'0' ; check for field width + bcc @notdigit + cmp #'9'+1 + bcs @notdigit + and #$0f + bne :+ ; check for leading 0 + inc fieldwcnt + bne :+ + lda #$80 + sta leadzero + pla + tay + jmp @argnext +: + pha ; multiply old value by 10 + asl fieldwidth + lda fieldwidth + asl + asl + clc + adc fieldwidth + sta fieldwidth + pla + clc ; add new value + adc fieldwidth + sta fieldwidth + pla + tay + jmp @argnext + +@notdigit: + cmp #'s' + beq @argstr + + cmp #'d' + beq @argint + + cmp #'x' + beq @arghex + + cmp #'c' + beq @argchar + +@argdone: + pla + tay + jmp @next + +@argstr: + jsr @argax + jsr console_strout + + jmp @argdone + +@argint: + jsr @argax + stax valptr + jsr @valax + jsr printint + + jmp @argdone + +@arghex: + jsr @argax + stax valptr + jsr @valax + jsr printhex + + jmp @argdone + +@argchar: + jsr @argax + stax valptr + ldy #0 + lda (valptr),y + jsr console_out + + jmp @argdone + +@argax: + ldy arg + lda (argptr),y + pha + iny + lda (argptr),y + tax + iny + sty arg + pla + rts + +@valax: + ldy #0 + lda (valptr),y + pha + iny + lda (valptr),y + tax + pla + rts + +@printx: + txa + lsr + lsr + lsr + lsr + tay + lda hex2asc,y + jsr console_out + txa + and #$0f + tay + lda hex2asc,y + jmp console_out + + +; print 16-bit hexadecimal number +printhex: + tay + and #$0f + sta num + 3 + tya + lsr + lsr + lsr + lsr + sta num + 2 + + txa + and #$0f + sta num + 1 + txa + lsr + lsr + lsr + lsr + sta num + + lda #4 + sec + sbc fieldwidth + tax + bpl :+ + jsr printlong +: + cpx #4 + beq @nowidth + +@printlead: + lda num,x + bne @printrest + lda #' ' + bit leadzero + bpl :+ + lda #'0' +: jsr console_out + inx + cpx #3 + bne @printlead + +@nowidth: + ldx #0 +: lda num,x + bne @printrest + inx + cpx #4 + bne :- + lda #'0' + jsr console_out + rts + +@printrest: + lda num,x + tay + lda hex2asc,y + jsr console_out + inx + cpx #4 + bne @printrest + rts + + +printlong: + lda #' ' + bit leadzero + bpl :+ + lda #'0' +: jsr console_out + inx + bne :- + rts + + +; print a 16-bit integer +printint: + stax int + + ldx #4 +@next: + lda #0 + sta num,x + jsr div10 + lda ext + sta num,x + dex + bpl @next + + lda fieldwidth + beq @nowidth + lda #5 + sec + sbc fieldwidth + tax + bpl :+ + jsr printlong +: +@printlead: + lda num,x + bne @print + + lda #' ' + bit leadzero + bpl :+ + lda #'0' +: jsr console_out + inx + cpx #5 + bne @printlead + beq @printzero + +@nowidth: + inx + cpx #5 + beq @printzero + lda num,x + beq @nowidth + +@print: + clc + adc #'0' + jsr console_out + inx + cpx #5 + beq @done +@printall: + lda num,x + jmp @print + +@done: + rts + +@printzero: + lda #'0' + jmp console_out + + +; 16/16-bit division, from the fridge +; int/aux -> int, remainder in ext +div10: + lda #0 + sta ext+1 + ldy #$10 +@dloop: + asl int + rol int+1 + rol + rol ext+1 + pha + cmp #10 + lda ext+1 + sbc #0 ; is this a nop? + bcc @div2 + sta ext+1 + pla + sbc #10 + pha + inc int +@div2: + pla + dey + bne @dloop + sta ext + rts + + + .rodata + +msg_unimplemented: + .byte "",0 + +hex2asc: + .byte "0123456789abcdef" + +esc_code: + .byte "eabfnrt", '\' +esc_count = * - esc_code +esc_char: + .byte 27, 7, 8, 12, 10, 13, 9, '\' diff --git a/client/ip65/tftp.s b/client/ip65/tftp.s new file mode 100644 index 0000000..cdf1515 --- /dev/null +++ b/client/ip65/tftp.s @@ -0,0 +1,357 @@ +;######################## +; minimal tftp implementation (client only) +; written by jonno@jamtronix.com 2009 +; +;######################## +; to use - +; set tftp_ip to host to download from (which can be broadcast address 255.255.255.255) +; set tftp_load_address to point to memory location that file will be downloaded to +; OR set tftp_load_address to $0000, and first 2 bytes of downloaded file will be treated as the load address +; set tftp_filename to null terminated filename to download +; then call tftp_download +; on exit: carry flag is set if there was an error. tftp_load_address will be set to address file loaded to (i.e. gets overwritten if originally set to $0000)f +;######################## + + TFTP_MAX_RESENDS=10 + TFTP_TIMER_MASK=$F8 ;mask lower two bits, means we wait for 8 x1/4 seconds + + .include "../inc/common.i" + + .exportzp tftp_filename + .export tftp_load_address + .export tftp_ip + .export tftp_download + + + .import ip65_process + + .import udp_add_listener + .import udp_remove_listener + + .import udp_callback + .import udp_send + + .import udp_inp + .import ip_inp + .importzp ip_src + .importzp udp_src_port + + + .importzp udp_data + + .import udp_send_dest + .import udp_send_src_port + .import udp_send_dest_port + .import udp_send_len + + .import copymem + .importzp copy_src + .importzp copy_dest + + .import timer_read + + .segment "IP65ZP" : zeropage + +tftp_filename: .res 2 + + .bss + +;packet offsets +tftp_inp = udp_inp + udp_data +tftp_outp: .res 128 +;everything after filename in a request at a relative address, not fixed, so don't bother defining offset constants + +tftp_server_port=69 +tftp_client_port_low_byte: .res 1 + +tftp_load_address: .res 2 +tftp_ip: .res 4 + +tftp_bytes_to_copy: .res 2 +tftp_current_memloc: .res 2 + +; tftp state machine +tftp_initializing = 1 ; initial state +tftp_rrq_sent=2 ; sent the read request, waiting for some data +tftp_receiving_file=3 ; we have received the first packet of file data +tftp_complete=4 ; we have received the final packet of file data +tftp_error=5 ; we got an error + +tftp_state: .res 1 ; current activity +tftp_timer: .res 1 +tftp_resend_counter: .res 1 +tftp_break_inner_loop: .res 1 +tftp_expected_block_number: .res 1 +tftp_block_number_to_ack: .res 1 +tftp_actual_server_port: .res 2 ;this is read from the reply - it is not (usually) the port # we send the RRQ to +tftp_actual_server_ip: .res 4 ;this is read from the reply - it may not be the IP we sent to (e.g. if we send to broadcast) + +tftp_just_set_new_load_address: .res 1 + +.code + +tftp_download: + + lda #tftp_initializing + sta tftp_state + sta tftp_expected_block_number ;(tftp_initializing=1) + ldax tftp_load_address + stax tftp_current_memloc + ldax #tftp_in + stax udp_callback + lda #$69 + inc tftp_client_port_low_byte ;each call to resolve uses a different client address + ldx tftp_client_port_low_byte ;so we don't get confused by late replies to a previous call + jsr udp_add_listener + + bcc :+ ;bail if we couldn't listen on the port we want + rts +: + + lda #TFTP_MAX_RESENDS + sta tftp_resend_counter +@outer_delay_loop: + jsr timer_read + txa + and #TFTP_TIMER_MASK + sta tftp_timer ;we only care about the high byte + lda #0 + sta tftp_break_inner_loop + lda tftp_state + cmp #tftp_initializing + bne @not_initializing + jsr send_read_request + jmp @inner_delay_loop + +@not_initializing: + cmp #tftp_error + bne @not_error + +@exit_with_error: + lda #$69 + ldx tftp_client_port_low_byte + jsr udp_remove_listener + sec + rts + +@not_error: + + cmp #tftp_complete + bne @not_complete + jsr send_ack ;send the ack for the last block + lda #$69 + ldx tftp_client_port_low_byte + jsr udp_remove_listener + rts + +@not_complete: + cmp #tftp_receiving_file + bne @not_receiving + jsr send_ack + jmp @inner_delay_loop +@not_receiving: + jsr send_read_request + +@inner_delay_loop: + + jsr ip65_process + lda tftp_break_inner_loop + bne @outer_delay_loop + jsr timer_read + txa + and #TFTP_TIMER_MASK + cmp tftp_timer + beq @inner_delay_loop + + dec tftp_resend_counter + bne @outer_delay_loop + jmp @exit_with_error + +send_read_request: + lda #tftp_initializing + sta tftp_state + ldax #$0100 ;opcode 01 = RRQ + stax tftp_outp + + ldx #$01 ;we inc x/y at start of loop, so + ldy #$ff ;set them to be 1 below where we want the copy to begin +@copy_filename_loop: + inx + iny + bmi @error_in_send ;if we get to 0x80 bytes, we've gone too far + lda (tftp_filename),y + sta tftp_outp,x + bne @copy_filename_loop + + ldy #$ff +@copy_mode_loop: + inx + iny + lda tftp_octet_mode,y + sta tftp_outp,x + bne @copy_mode_loop + + inx + txa + ldx #0 + stax udp_send_len + + lda #$69 + ldx tftp_client_port_low_byte + stax udp_send_src_port + + ldx #3 ; set destination address +: lda tftp_ip,x + sta udp_send_dest,x + dex + bpl :- + + ldax #tftp_server_port ; set destination port + stax udp_send_dest_port + ldax #tftp_outp + jsr udp_send + bcs @error_in_send + lda #tftp_rrq_sent + sta tftp_state + rts +@error_in_send: + + sec + rts + +send_ack: + ldax #$0400 ;opcode 04 = ACK + stax tftp_outp + ldx tftp_block_number_to_ack + stax tftp_outp+2 + lda #$69 + ldx tftp_client_port_low_byte + stax udp_send_src_port + + lda tftp_actual_server_ip + sta udp_send_dest + lda tftp_actual_server_ip+1 + sta udp_send_dest+1 + lda tftp_actual_server_ip+2 + sta udp_send_dest+2 + lda tftp_actual_server_ip+3 + sta udp_send_dest+3 + ldx tftp_actual_server_port + lda tftp_actual_server_port+1 + stax udp_send_dest_port + ldax #04 + stax udp_send_len + + ldax #tftp_outp + jsr udp_send + rts + + +tftp_in: + + lda tftp_inp+1 ;get the opcode + cmp #5 + bne @not_an_error +@recv_error: + lda #tftp_error + sta tftp_state + + rts +@not_an_error: + + cmp #3 + beq :+ + jmp @not_data_block +: + + lda #0 + sta tftp_just_set_new_load_address ;clear the flag + clc + lda tftp_load_address + adc tftp_load_address+1 ;is load address currently $0000? + bne @dont_set_load_address + ldax udp_inp+$0c ;get first two bytes of data + stax tftp_load_address ;make them the new load adress + stax tftp_current_memloc ;also the current memory destination + lda #1 ;set the flag + sta tftp_just_set_new_load_address + +@dont_set_load_address: + lda tftp_inp+3 ;get the (low byte) of the data block + bmi @recv_error ;if we get to block $80, we've d/led more than 64k! + cmp tftp_expected_block_number + beq :+ + jmp @not_expected_block_number +: + ;this is the block we wanted + sta tftp_block_number_to_ack + inc tftp_expected_block_number + lda #tftp_receiving_file + sta tftp_state + lda #TFTP_MAX_RESENDS + sta tftp_resend_counter + lda #1 + sta tftp_break_inner_loop + + ldax udp_inp+udp_src_port + stax tftp_actual_server_port + ldax ip_inp+ip_src + stax tftp_actual_server_ip + ldax ip_inp+ip_src+2 + stax tftp_actual_server_ip+2 + + + lda tftp_just_set_new_load_address + bne @skip_first_2_bytes_in_calculating_header_length + lda udp_inp+5 ;get the low byte of udp packet length + sec + sbc #$0c ;take off the length of the UDP header+OPCODE + BLOCK + + jmp @adjusted_header_length +@skip_first_2_bytes_in_calculating_header_length: + lda udp_inp+5 ;get the low byte of udp packet length + sec + sbc #$0e ;take off the length of the UDP header+OPCODE + BLOCK + first 2 bytes (memory location) +@adjusted_header_length: + + sta tftp_bytes_to_copy + lda udp_inp+4 ;get high byte of the length of the UDP packet + sbc #0 + sta tftp_bytes_to_copy+1 + + lda tftp_just_set_new_load_address + bne @skip_first_2_bytes_in_calculating_copy_src + ldax #udp_inp+$0c + jmp @got_copy_src +@skip_first_2_bytes_in_calculating_copy_src: + ldax #udp_inp+$0e +@got_copy_src: + stax copy_src + ldax tftp_current_memloc + stax copy_dest + ldax tftp_bytes_to_copy + jsr copymem + clc + lda tftp_bytes_to_copy ;update the location where the next data will go + adc tftp_current_memloc + sta tftp_current_memloc + lda tftp_bytes_to_copy+1 + adc tftp_current_memloc+1 + sta tftp_current_memloc+1 + lda udp_inp+4 ;check the length of the UDP packet + cmp #02 + bne @last_block + + lda udp_inp+5 + cmp #$0c + bne @last_block +@not_data_block: +@not_expected_block_number: + rts + +@last_block: + lda #tftp_complete + sta tftp_state + rts +.rodata + tftp_octet_mode: .asciiz "OCTET" \ No newline at end of file diff --git a/client/ip65/timer.s b/client/ip65/timer.s new file mode 100644 index 0000000..fc1b199 --- /dev/null +++ b/client/ip65/timer.s @@ -0,0 +1,38 @@ +;originally from Per Olofsson's IP65 library - http://www.paradroid.net/ip65 + +; timer routines +; +; the timer should be a 16-bit counter that's incremented by about +; 1000 units per second. it doesn't have to be particularly accurate, +; if you're working with e.g. a 60 Hz VBLANK IRQ, adding 17 to the +; counter every frame would be just fine. + + + .include "../inc/common.i" + + + .export timer_timeout + .import timer_read + + .bss + +time: .res 2 + + + .code + +; check if value in A/X is smaller than current timer value +timer_timeout: + pha + txa + pha + jsr timer_read + stax time + pla + tax + pla + sec ; subtract current value + sbc time + txa + sbc time + 1 + rts ; clc = timeout, sec = no timeout diff --git a/client/ip65/udp.s b/client/ip65/udp.s new file mode 100644 index 0000000..a79be49 --- /dev/null +++ b/client/ip65/udp.s @@ -0,0 +1,313 @@ +;originally from Per Olofsson's IP65 library - http://www.paradroid.net/ip65 + +.include "../inc/common.i" + + ;.import dbg_dump_udp_header + + + .export udp_init + .export udp_process + .export udp_add_listener + .export udp_remove_listener + .export udp_send + + .export udp_callback + + .export udp_inp + .export udp_outp + + .exportzp udp_src_port + .exportzp udp_dest_port + .exportzp udp_len + .exportzp udp_cksum + .exportzp udp_data + + .export udp_send_dest + .export udp_send_src_port + .export udp_send_dest_port + .export udp_send_len + + + .import ip_calc_cksum + .import ip_send + .import ip_create_packet + .import ip_inp + .import ip_outp + .importzp ip_cksum_ptr + .importzp ip_header_cksum + .importzp ip_src + .importzp ip_dest + .importzp ip_data + .importzp ip_proto + .importzp ip_proto_udp + .importzp ip_id + .importzp ip_len + + .import copymem + .importzp copy_src + .importzp copy_dest + + .import cfg_ip + + + .bss + +; argument for udp_add_listener +udp_callback: .res 2 + +; arguments for udp_send +udp_send_dest: .res 4 +udp_send_src_port: .res 2 +udp_send_dest_port: .res 2 +udp_send_len: .res 2 + +; udp listener callbacks +udp_cbmax = 4 +udp_cbtmp: .res 3 ; temporary vector +udp_cbveclo: .res udp_cbmax ; table of listener vectors (lsb) +udp_cbvechi: .res udp_cbmax ; table of listener vectors (msb) +udp_cbportlo: .res udp_cbmax ; table of ports (lsb) +udp_cbporthi: .res udp_cbmax ; table of ports (msb) +udp_cbcount: .res 1 ; number of active listeners + +; udp packet offsets +udp_inp = ip_inp + ip_data +udp_outp = ip_outp + ip_data +udp_src_port = 0 +udp_dest_port = 2 +udp_len = 4 +udp_cksum = 6 +udp_data = 8 + +; virtual header +udp_vh = udp_outp - 12 +udp_vh_src = 0 +udp_vh_dest = 4 +udp_vh_zero = 8 +udp_vh_proto = 9 +udp_vh_len = 10 + + +; temp for port comparison +port: .res 2 + + + .code + +; initialize udp +udp_init: + lda #0 + sta udp_cbcount + lda #$4c ; jmp addr + sta udp_cbtmp + rts + + +; process incoming udp packet +udp_process: + lda udp_cbcount ; any installed udp listeners? + beq @drop + + tax ; check ports + dex +@checkport: + lda udp_cbportlo,x + cmp udp_inp + udp_dest_port + 1 + bne :+ + lda udp_cbporthi,x + cmp udp_inp + udp_dest_port + beq @handle +: dex + bpl @checkport + +@drop: + sec + rts + +@handle: + lda udp_cbveclo,x ; copy vector + sta udp_cbtmp + 1 + lda udp_cbvechi,x + sta udp_cbtmp + 2 + jsr udp_cbtmp ; call listener + clc + rts + + +; add an udp listener +; udp port in A/X, vector in udp_callback +udp_add_listener: + sta port + stx port + 1 + + ldy udp_cbcount ; any listeners at all? + beq @add + cpy #udp_cbmax ; max? + beq @full + ldy #0 +@check: + lda udp_cbportlo,y ; check if port is already handled + cmp port + bne :+ + lda udp_cbporthi,y + cmp port + 1 + beq @busy +: iny + cpy udp_cbcount + bne @check +@add: + inc udp_cbcount ; increase counter + sta udp_cbportlo,y ; add port + txa + sta udp_cbporthi,y ; add port + lda udp_callback ; and vector + sta udp_cbveclo,y + lda udp_callback + 1 + sta udp_cbvechi,y + + clc + rts +@full: +@busy: + sec + rts + + +; remove an udp listener +; udp port in A/X +udp_remove_listener: + sta port + stx port + 1 + + ldy udp_cbcount ; any listeners installed? + beq @notfound +@check: + lda udp_cbportlo,y ; check if port is handled + cmp port + bne :+ + lda udp_cbporthi,y + cmp port + 1 + beq @remove +: iny + cpy udp_cbcount + bne @check +@notfound: + sec + rts +@remove: + tya ; number of listeners below + eor #$ff + clc + adc udp_cbcount + beq @done +@move: + tax ; number of items to move +: lda udp_cbportlo + 1,y ; move ports + sta udp_cbportlo,y + lda udp_cbporthi + 1,y + sta udp_cbporthi,y + lda udp_cbveclo + 1,y ; move vectors + sta udp_cbveclo,y + lda udp_cbvechi + 1,y + sta udp_cbvechi,y + iny + dex + bne :- +@done: + dec udp_cbcount ; decrement counter + clc + rts + + +; send udp packet +; +; but first: +; +; set destination address +; set source port +; set destination port +; set length +udp_send: + stax copy_src ; copy data to output buffer + ldax #udp_outp + udp_data + stax copy_dest + ldax udp_send_len + jsr copymem + + ldx #3 ; copy virtual header addresses +: lda udp_send_dest,x + sta udp_vh + udp_vh_dest,x ; set virtual header destination + lda cfg_ip,x + sta udp_vh + udp_vh_src,x ; set virtual header source + dex + bpl :- + + lda udp_send_src_port ; copy source port + sta udp_outp + udp_src_port + 1 + lda udp_send_src_port + 1 + sta udp_outp + udp_src_port + + lda udp_send_dest_port ; copy destination port + sta udp_outp + udp_dest_port + 1 + lda udp_send_dest_port + 1 + sta udp_outp + udp_dest_port + + lda #ip_proto_udp + sta udp_vh + udp_vh_proto + + lda #0 ; clear checksum + sta udp_outp + udp_cksum + sta udp_outp + udp_cksum + 1 + sta udp_vh + udp_vh_zero ; clear virtual header zero byte + + ldax #udp_vh ; checksum pointer to virtual header + stax ip_cksum_ptr + + lda udp_send_len ; copy length + 8 + clc + adc #8 + sta udp_outp + udp_len + 1 ; lsb for udp header + sta udp_vh + udp_vh_len + 1 ; lsb for virtual header + tay + lda udp_send_len + 1 + adc #0 + sta udp_outp + udp_len ; msb for udp header + sta udp_vh + udp_vh_len ; msb for virtual header + + tax ; length to A/X + tya + + clc ; add 12 bytes for virtual header + adc #12 + bcc :+ + inx +: + jsr ip_calc_cksum ; calculate checksum + stax udp_outp + udp_cksum + + ldx #3 ; copy addresses +: lda udp_send_dest,x + sta ip_outp + ip_dest,x ; set ip destination address + dex + bpl :- + + jsr ip_create_packet ; create ip packet template + + lda udp_outp + udp_len + 1 ; ip len = udp len + 20 + ldx udp_outp + udp_len + clc + adc #20 + bcc :+ + inx +: sta ip_outp + ip_len + 1 ; set length + stx ip_outp + ip_len + + ldax #$1234 ; set ID + stax ip_outp + ip_id + + lda #ip_proto_udp ; set protocol + sta ip_outp + ip_proto + + ;jsr dbg_dump_udp_header + + jmp ip_send ; send packet, sec on error diff --git a/client/test/Makefile b/client/test/Makefile new file mode 100644 index 0000000..d641b7f --- /dev/null +++ b/client/test/Makefile @@ -0,0 +1,45 @@ +CC=cl65 +AS=ca65 +LD=ld65 +CFLAGS=-Oirs -t $(TARGET) +AFLAGS= + + +IP65LIB=../ip65/ip65.lib +C64NETLIB=../drivers/c64net.lib +APPLE2NETLIB=../drivers/apple2net.lib + +INCFILES=\ + ../inc/common.i\ + ../inc/commonprint.i\ + ../inc/net.i\ + +%.o: %.c + $(CC) -c $(CFLAGS) $< + +%.o: %.s + $(AS) $(AFLAGS) $< + +%.bin: %.o $(IP65LIB) $(APPLE2NETLIB) $(INCFILES) ../cfg/a2bin.cfg + $(LD) -m $*.map -C ../cfg/a2bin.cfg -o $*.bin $(AFLAGS) $< $(IP65LIB) $(APPLE2NETLIB) + +%.prg: %.o $(IP65LIB) $(C64NETLIB) $(INCFILES) ../cfg/c64prg.cfg + $(LD) -m $*.map -C ../cfg/c64prg.cfg -o $*.prg $(AFLAGS) $< $(IP65LIB) $(C64NETLIB) + +ip65test.dsk: testdns.bin + dsktool.rb --init dos33 ip65test.dsk -a testdns.bin -t B + + +all: \ + ip65test.dsk \ + testdns.bin \ + testdns.prg \ + + +clean: + rm -f *.o + rm -f testdns.prg testdns.map testdns.bin + rm -f ip65test.dsk + +distclean: clean + rm -f *~ diff --git a/client/test/testdns.s b/client/test/testdns.s new file mode 100644 index 0000000..6274fb8 --- /dev/null +++ b/client/test/testdns.s @@ -0,0 +1,136 @@ + .include "../inc/common.i" + .include "../inc/commonprint.i" + .include "../inc/net.i" + + .import exit_to_basic + + .import dns_set_hostname + .import dns_resolve + .import dns_ip + .import dns_status + .import __CODE_LOAD__ + .import __CODE_SIZE__ + .import __RODATA_SIZE__ + .import __DATA_SIZE__ + + + .segment "STARTUP" ;this is what gets put at the start of the file on the C64 + + .word basicstub ; load address + +basicstub: + .word @nextline + .word 2003 + .byte $9e + .byte <(((init / 1000) .mod 10) + $30) + .byte <(((init / 100 ) .mod 10) + $30) + .byte <(((init / 10 ) .mod 10) + $30) + .byte <(((init ) .mod 10) + $30) + .byte 0 +@nextline: + .word 0 + +.segment "EXEHDR" ;this is what gets put an the start of the file on the Apple 2 + .addr __CODE_LOAD__-$11 ; Start address + .word __CODE_SIZE__+__RODATA_SIZE__+__DATA_SIZE__+4 ; Size + jmp init + +.code + +init: + + jsr print_cr + jsr print_ip_config + + init_ip_via_dhcp + +; jsr overwrite_with_hardcoded_dns_server + jsr print_ip_config + + ldax #hostname_1 + jsr do_dns_query + + ldax #hostname_2 + jsr do_dns_query + + ldax #hostname_3 + jsr do_dns_query + + ldax #hostname_4 + jsr do_dns_query + + ldax #hostname_5 + jsr do_dns_query + + ldax #hostname_6 + jsr do_dns_query + + jmp exit_to_basic + + +do_dns_query: + pha + jsr print + lda #' ' + jsr print_a + lda #':' + jsr print_a + lda #' ' + jsr print_a + pla + jsr dns_set_hostname + jsr dns_resolve + bcc :+ + ldax #dns_lookup_failed_msg + jsr print + jmp @print_dns_status +: + ldax #dns_ip + jsr print_dotted_quad +@print_dns_status: + jsr print_cr + lda dns_status + jsr print_hex + lda dns_status+1 + jsr print_hex + jsr print_cr + rts + +overwrite_with_hardcoded_dns_server: + ldx #3 +: + lda hardcoded_dns_server,x + sta cfg_dns,x + dex + bpl :- + rts + + + + .rodata + + +buffer1: .res 256 +hostname_1: + .byte "SLASHDOT.ORG",0 ;this should be an A record + +hostname_2: + .byte "VICTA.JAMTRONIX.COM",0 ;this should be a CNAME + +hostname_3: + .byte "WWW.JAMTRONIX.COM",0 ;this should be another CNAME + +hostname_4: + .byte "FOO.BAR.BOGUS",0 ;this should fail + +hostname_5: ;this currently fails, would be nice if we noticed it was an IP address + .byte "111.22.3.4",0 + +hostname_6: ;make sure doesn't get treated as a number + .byte "3COM.COM",0 + +hardcoded_dns_server: +;.byte 61,9,195,193 +;.byte 64,127,100,12 +.byte 205,171,3,65 +.byte 69,111,95,106