git-svn-id: http://svn.code.sf.net/p/netboot65/code@15 93682198-c243-4bdb-bd91-e943c89aac3b

This commit is contained in:
jonnosan 2009-01-22 01:27:03 +00:00
parent 1f9aae4c6b
commit 6d88a6a03e
44 changed files with 5085 additions and 0 deletions

19
client/cfg/a2bin.cfg Normal file
View File

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

View File

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

16
client/cfg/c64prg.cfg Normal file
View File

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

20
client/cfg/rrbin.cfg Normal file
View File

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

44
client/clients/Makefile Normal file
View File

@ -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 *~

View File

@ -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<<infile
outfile<<PAD_BYTE*(FILE_LENGTH-infile.length)
outfile.close

BIN
client/clients/hello Normal file

Binary file not shown.

163
client/clients/rrnetboot.s Normal file
View File

@ -0,0 +1,163 @@
;#############
;
; This will boot a C64 with RR-NET from the network
; requires
; 1) a DHCP server, and
; 2) a TFTP server that responds to requests on the broadcast address (255.255.255.255) and that will serve a file called 'BOOTC64.PRG'.
; the prg file can be either BASIC or M/L, and up to 30K in length.
;
; jonno@jamtronix.com - January 2009
;
.include "../inc/common.i"
.include "../inc/commonprint.i"
.include "../inc/net.i"
.importzp tftp_filename
.import tftp_load_address
.import tftp_ip
.import tftp_download
.import cfg_tftp_server
.import copymem
.importzp copy_src
.importzp copy_dest
.import __DATA_LOAD__
.import __DATA_RUN__
.import __DATA_SIZE__
.bss
temp_bin: .res 1
temp_bcd: .res 2
bin_file_jmp: .res 3
.segment "CARTRIDGE_HEADER"
.word init ;cold start vector
.word init ;warm start vector
.byte $C3,$C2,$CD,$38,$30 ; "CBM80"
.code
init:
;first let the kernal do a normal startup
sei
jsr $fda3 ;initialize CIA I/O
jsr $fd50 ;RAM test, set pointers
jsr $fd15 ;set vectors for KERNAL
jsr $ff5B ;init. VIC
cli ;KERNAL init. finished
jsr $e453 ;set BASIC vectors
jsr $e3bf ;initialize zero page
ldax #startup_msg
jsr print
;relocate our r/w data
ldax #__DATA_LOAD__
stax copy_src
ldax #__DATA_RUN__
stax copy_dest
ldax #__DATA_SIZE__
jsr copymem
init_ip_via_dhcp
jsr print_ip_config
ldx #3
:
lda cfg_tftp_server,x
sta tftp_ip,x
dex
bpl :-
ldax #$0000 ;load address will be first 2 bytes of file we dowload (LO/HI order)
stax tftp_load_address
ldax #downloading_msg
jsr print
ldax #tftp_file
jsr download
bcc @file_downloaded_ok
jmp bad_boot
@file_downloaded_ok:
;check whether the file we just downloaded was a BASIC prg
lda tftp_load_address
cmp #01
bne @not_a_basic_file
lda tftp_load_address+1
cmp #$08
bne @not_a_basic_file
jsr $e453 ;set BASIC vectors
jsr $e3bf ;initialize BASIC
jsr $a86e
jsr $a533 ; re-bind BASIC lines
ldx $22 ;load end-of-BASIC pointer (lo byte)
ldy $23 ;load end-of-BASIC pointer (hi byte)
stx $2d ;save end-of-BASIC pointer (lo byte)
sty $2e ;save end-of-BASIC pointer (hi byte)
jsr $a659 ; CLR (reset variables)
jmp $a7ae ; jump to BASIC interpreter loop
@not_a_basic_file:
lda #$4C ;opcode for JMP
sta bin_file_jmp
ldax tftp_load_address
stax bin_file_jmp+1
jsr bin_file_jmp
rts
bad_boot:
jmp bad_boot
download:
stax tftp_filename
jsr print
jsr print_cr
jsr tftp_download
bcc :+
ldax #tftp_download_fail_msg
jsr print
sec
rts
:
ldax #tftp_download_ok_msg
jsr print
clc
rts
.rodata
startup_msg: .byte "RR-NET NETWORK BOOK CLIENT V0.1",13,0
downloading_msg: .asciiz "DOWNLOADING "
tftp_file:
.asciiz "BOOTC64.PRG"
tftp_download_fail_msg:
.byte "DOWNLOAD FAILED", 13, 0
tftp_download_ok_msg:
.byte "DOWNLOAD OK", 13, 0

236
client/clients/utherboot.s Normal file
View File

@ -0,0 +1,236 @@
;#############
;
; This will boot an Apple 2 with uthernet in slot 3 from the network
; requires
; 1) a DHCP server, and
; 2) a TFTP server that responds to requests on the broadcast address (255.255.255.255) and that will serve a file called 'BOOTA2.BIN'.
;
; jonno@jamtronix.com - January 2009
;
.include "../inc/common.i"
.include "../inc/commonprint.i"
.include "../inc/net.i"
.import cls
.importzp tftp_filename
.import tftp_load_address
.import tftp_ip
.import tftp_download
.import copymem
.importzp copy_src
.importzp copy_dest
.import __STARTUP_LOAD__
.import __STARTUP_SIZE__
.import __BSS_LOAD__
.import __DATA_LOAD__
.import __DATA_RUN__
.import __DATA_SIZE__
.import __RODATA_LOAD__
.import __RODATA_RUN__
.import __RODATA_SIZE__
.import __CODE_LOAD__
.import __CODE_RUN__
.import __CODE_SIZE__
.segment "PAGE3"
disable_language_card: .res 3
bin_file_jmp: .res 3
; ------------------------------------------------------------------------
.segment "EXEHDR"
.addr __STARTUP_LOAD__ ; Start address
.word __STARTUP_SIZE__+__CODE_SIZE__+__RODATA_SIZE__+__DATA_SIZE__+4 ; Size
; ------------------------------------------------------------------------
.segment "STARTUP"
lda $c089 ;enable language : card read ROM, write RAM, BANK 1
;copy the monitor rom on to the language card
ldax #$f800
stax copy_src
stax copy_dest
ldax #$0800
jsr startup_copymem
lda $c08b ;enable language : card read RAM, write RAM, BANK 1
lda $c08b ;this soft switch needs to be read twice
;relocate the CODE segment
ldax #__CODE_LOAD__
stax copy_src
ldax #__CODE_RUN__
stax copy_dest
ldax #__CODE_SIZE__
jsr startup_copymem
;relocate the RODATA segment
ldax #__RODATA_LOAD__
stax copy_src
ldax #__RODATA_RUN__
stax copy_dest
ldax #__RODATA_SIZE__
jsr startup_copymem
;relocate the DATA segment
ldax #__DATA_LOAD__
stax copy_src
ldax #__DATA_RUN__
stax copy_dest
ldax #__DATA_SIZE__
jsr startup_copymem
jmp init
; copy memory
; set copy_src and copy_dest, length in A/X
end: .res 1
startup_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
.code
init:
jsr cls
ldax #startup_msg
jsr print
jsr print_cr
init_ip_via_dhcp
bcs bad_boot
jsr print_ip_config
ldx #3
:
lda cfg_tftp_server,x
sta tftp_ip,x
dex
bpl :-
ldax #$0000 ;load address will be first 2 bytes of file we download (LO/HI order)
stax tftp_load_address
ldax #downloading_msg
jsr print
ldax #tftp_file
jsr download
bcc @file_downloaded_ok
jmp bad_boot
@file_downloaded_ok:
;set up to jump to where we just d/led the file to
lda #$4C ;opcode for JMP
sta bin_file_jmp
ldax tftp_load_address
stax bin_file_jmp+1
;but before we go, we need to shift the file down by 2 bytes (to skip over the file length)
ldax tftp_load_address
stax copy_dest
clc
adc #02
bcc :+
inx
:
stax copy_src
ldy #1
lda (copy_dest),y ;currently this is the high byte of the length
tax
dey
lda (copy_dest),y ;currently this is the low byte of the length
jsr copymem
;now make the 'turn off language card' routine
lda #$AD ;$AD=LDA
sta disable_language_card
lda #$82 ;low byte of soft switch
sta disable_language_card+1
lda #$c0 ;high byte of soft switch
sta disable_language_card+2
jmp disable_language_card
bad_boot:
jmp $3d0
download:
stax tftp_filename
jsr print
jsr print_cr
jsr tftp_download
bcc :+
ldax #tftp_download_fail_msg
jsr print
sec
rts
:
ldax #tftp_download_ok_msg
jsr print
clc
rts
.rodata
downloading_msg: .asciiz "DOWNLOADING "
tftp_file:
.asciiz "BOOTA2.BIN"
tftp_download_fail_msg:
.asciiz "DOWNLOAD FAILED"
tftp_download_ok_msg:
.asciiz "DOWNLOAD OK"
startup_msg: .byte "UTHERNET NETWORK BOOT CLIENT V0.1",0

View File

@ -0,0 +1,15 @@
WE NEED:
- about 4K of BSS space for ethernet buffers
- about 6K of code
BOOT FROM DISK (UTHERBOOT.BIN)
- loads in at $0800
- copies monitor rom to same spot in language card (F800-FFFF)
- relocates to $D000-$C000 in the language card (bank 1 only)
- makes language card active (bank 1)
- downloads BOOTA2.BIN via tftp
- turns off language card
- jumps to start of of BOOTA2.BIN

View File

@ -0,0 +1,46 @@
DOS 3.3 MEMORY LAYOUT
$C000 --- TOP OF RAM
$B600 --- RWTS
$AAC9 --- FILE MANAGER
$9D00 --- MAIN DOS ROUTINES
$9600 -- DOS FILE BUFFERS (MAXFILES 3)
BD00 - RWTS main entry
- call with AY pointing at IOB
Input/Output Control Block
OFFSET DESCRIPTION
$00 Table Type (must be 1)
$01 Slot Number times 0x10
$02 Drive Number ($01 or $02)
$03 Volume Number ($00 matches any volume)
$04 Track Number
$05 Sector Number
$06/$07 Address (lo/hi) of Device Characteristics Table
$08/$09 Address (lo/hi) of 256 byte buffer for READ/WRITE
$0A Not Used
$0B byte count for partial sector
$0c Command Code
$00 = SEEK
$01 = READ
$02 = WRITE
$04 = FORMAT
$0d Return Code (if non zero, carry flag should be set)
$00 = no error
$08 = error during initialization
$10 = write protect error
$20 = volume mismatch error
$40 = drive error
$80 = read error (obsolete)
$0e volume number of last access
$0f slot number of last acces * 16
$10 drive number of last access

32
client/drivers/Makefile Normal file
View File

@ -0,0 +1,32 @@
AS=ca65
LD=ld65
AFLAGS=
%.o: %.c
$(CC) -c $(CFLAGS) $<
%.o: %.s
$(AS) $(AFLAGS) $<
DRIVERS=\
apple2net.lib \
c64net.lib \
all: $(DRIVERS)
apple2net.lib: a2print.o uthernet.o a2timer.o a2kernal.o
ar65 a apple2net.lib $^
c64net.lib: c64print.o rr-net.o c64timer.o c64kernal.o
ar65 a c64net.lib $^
clean:
rm -f *.o
rm -f apple2net.lib
rm -f c64net.lib
distclean: clean
rm -f *~

View File

@ -0,0 +1,5 @@
.export exit_to_basic
.code
exit_to_basic:
jmp $3d0

21
client/drivers/a2print.s Normal file
View File

@ -0,0 +1,21 @@
.export print_a
.export print_cr
.export cls
.code
print_a:
ora #$80 ;turn ASCII into Apple 2 screen codes
jmp $fdf0
print_cr:
jmp $fd8e
cls:
jmp $fc58
rts

40
client/drivers/a2timer.s Normal file
View File

@ -0,0 +1,40 @@
; timer routines
;
; unfortunately the standard Apple 2 has no CIA or VBI, so for the moment, we will
; make each call to 'timer_read' delay for a little while
; this kludge will make the polling loops work at least
;
; timer_read is meant to return a counter with millisecond resolution
.include "../inc/common.i"
.export timer_init
.export timer_read
.bss
current_time_value: .res 2
.code
timer_init:
ldax #0
stax current_time_value
rts
; return the current value (actually, delay a while, then update current value, then return it in ax)
timer_read:
lda #111
jsr $fca8 ;wait for about 33ms
clc
lda #33
adc current_time_value
sta current_time_value
bcc :+
inc current_time_value+1
:
ldax current_time_value
rts

View File

@ -0,0 +1,5 @@
.export exit_to_basic
.code
exit_to_basic:
jmp $a7ae ; jump to BASIC interpreter loop

13
client/drivers/c64print.s Normal file
View File

@ -0,0 +1,13 @@
.export print_a
.export print_cr
.data
print_a = $ffd2
.code
print_cr:
lda #13
jmp $ffd2

46
client/drivers/c64timer.s Normal file
View File

@ -0,0 +1,46 @@
; 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_init
.export timer_read
.code
; initialize timers
timer_init:
lda #$80 ; stop timers
sta $dd0e
sta $dd0f
ldax #999 ; timer A to 1000 cycles
stax $dd04
ldax #$ffff ; timer B to max cycles
stax $dd06
lda #$81 ; timer A in continuous mode
sta $dd0e
lda #$c1 ; timer B to count timer A underflows
sta $dd0f
rts
; return the current value
timer_read:
lda $dd07 ; cia counts backwards, return inverted value
eor #$ff
tax
lda $dd06
eor #$ff
rts

35
client/drivers/rr-net.s Normal file
View File

@ -0,0 +1,35 @@
; RR-Net driver
.export cs_init
.export cs_packet_page
.export cs_packet_data
.export cs_rxtx_data
.export cs_tx_cmd
.export cs_tx_len
.export cs_driver_name
rr_ctl = $de01
;cs_irq = $de00
cs_packet_page = $de02
cs_packet_data = $de04
;cs_packet_data2 = $de06
cs_rxtx_data = $de08
;cs_rxtx_data2 = $de0a
cs_tx_cmd = $de0c
cs_tx_len = $de0e
.code
cs_init:
lda rr_ctl
ora #1
sta rr_ctl
rts
.rodata
cs_driver_name:
.asciiz "RR-NET"

29
client/drivers/uthernet.s Normal file
View File

@ -0,0 +1,29 @@
; uthernet driver
; for the moment, always assume slot 3
.export cs_init
.export cs_packet_page
.export cs_packet_data
.export cs_rxtx_data
.export cs_tx_cmd
.export cs_tx_len
.export cs_driver_name
cs_rxtx_data = $c0b0
cs_tx_cmd = $c0b4
cs_tx_len = $c0b6
cs_packet_page = $c0ba
cs_packet_data = $c0bc
.code
cs_init:
rts
.rodata
cs_driver_name:
.byte "UTHERNET",0

17
client/inc/common.i Normal file
View File

@ -0,0 +1,17 @@
; load A/X macro
.macro ldax arg
.if (.match (.left (1, arg), #)) ; immediate mode
lda #<(.right (.tcount (arg)-1, arg))
ldx #>(.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

232
client/inc/commonprint.i Normal file
View File

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

41
client/inc/net.i Normal file
View File

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

22
client/inc/petscii.i Normal file
View File

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

55
client/inc/printf.i Normal file
View File

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

43
client/ip65/Makefile Normal file
View File

@ -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 *~

448
client/ip65/arp.s Normal file
View File

@ -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 ; set packet length
sta eth_outp_len
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_len + 1
; 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 ; set packet length
sta eth_outp_len
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

21
client/ip65/config.s Normal file
View File

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

54
client/ip65/copymem.s Normal file
View File

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

7
client/ip65/cs8900a.i Normal file
View File

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

275
client/ip65/cs8900a.s Normal file
View File

@ -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
ldy #>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 ; set packet pointer
sta eth_packet
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 ; select bus status register
sta cs_packet_page
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

130
client/ip65/debug.s Normal file
View File

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

448
client/ip65/dhcp.s Normal file
View File

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

414
client/ip65/dns.s Normal file
View File

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

55
client/ip65/eth.s Normal file
View File

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

226
client/ip65/icmp.s Normal file
View File

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

410
client/ip65/ip.s Normal file
View File

@ -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 ; borrow copymem zp...
lda ip_outp + ip_len
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

76
client/ip65/ip65.s Normal file
View File

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

396
client/ip65/printf.s Normal file
View File

@ -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 "<unimplemented>",0
hex2asc:
.byte "0123456789abcdef"
esc_code:
.byte "eabfnrt", '\'
esc_count = * - esc_code
esc_char:
.byte 27, 7, 8, 12, 10, 13, 9, '\'

357
client/ip65/tftp.s Normal file
View File

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

38
client/ip65/timer.s Normal file
View File

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

313
client/ip65/udp.s Normal file
View File

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

45
client/test/Makefile Normal file
View File

@ -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 *~

136
client/test/testdns.s Normal file
View File

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