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

This commit is contained in:
jonnosan 2009-04-16 05:55:32 +00:00
parent d0fb0d16ed
commit ce3609f2bb
10 changed files with 263 additions and 74 deletions

View File

@ -5,7 +5,7 @@ MEMORY {
IP65ZP: start = $A3, size = $0E, type = rw, define = yes;
HEADER: start = $8000, size = $18, file = %O;
ROM: start = $8018, size = $1F00, define = yes, file = %O;
RAM: start = $C080, size = $0f80, define = yes;
RAM: start = $C010, size = $0fE0, define = yes;
}

View File

@ -24,14 +24,17 @@ get_key:
;inputs: none
;outputs: sec if RUN/STOP pressed, clear otherwise
check_for_abort_key:
lda $cb ;current key pressed
cmp #$3F
bne :+
jsr $ffe4 ;get the keypress out of the buffer
sec
:
clc
rts
lda $cb ;current key pressed
cmp #$3F
bne @not_abort
@flush_loop:
jsr $ffe4
bne @flush_loop
sec
rts
@not_abort:
clc
rts
;cribbed from http://codebase64.org/doku.php?id=base:robust_string_input
;======================================================================

View File

@ -1,7 +1,7 @@
.bss
;global scratch buffer that DHCP/DNS and others can use while building outbound packets.
;global scratch buffer that DHCP/DNS/TFTP and others can use while building outbound packets.
;you need to be careful if using this that you don't call a function that also uses it.
;if this is reversed for higher level protocols, the likelyhood of collision is low.
.export output_buffer
output_buffer: .res 256
output_buffer: .res 520

View File

@ -1,5 +1,5 @@
;minimal tftp implementation (client only)
;supports file download (not upload) and custom directory listing (using non-standard tftp opcode of 0x65)
;supports file download and upload and custom directory listing (using non-standard tftp opcode of 0x65)
TFTP_MAX_RESENDS=10
@ -15,12 +15,13 @@
.export tftp_load_address
.export tftp_ip
.export tftp_download
.export tftp_upload
.export tftp_directory_listing
.export tftp_data_block_length
.export tftp_set_callback_vector
.export tftp_data_block_length
.export tftp_clear_callbacks
.import output_buffer
.import ip65_process
.import ip65_error
@ -29,7 +30,7 @@
.import output_buffer
.import udp_callback
.import udp_send
.import check_for_abort_key
.import udp_inp
.import ip_inp
.importzp ip_src
@ -58,7 +59,6 @@ tftp_filename: .res 2 ;name of file to d/l or filemask to get directory listing
;packet offsets
tftp_inp = udp_inp + udp_data
tftp_outp = output_buffer
;= output_buffer
;everything after filename in a request at a relative address, not fixed, so don't bother defining offset constants
@ -69,22 +69,22 @@ tftp_load_address: .res 2 ;address file will be (or was) downloaded to
tftp_ip: .res 4 ;ip address of tftp server - set to 255.255.255.255 (broadcast) to send request to all tftp servers on local lan
tftp_data_block_length: .res 2
tftp_send_len: .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_initial_request_sent=2 ; sent the RRQ or WRQ, waiting for some data
tftp_transmission_in_progress=3 ; we have sent/received the first packet of file data
tftp_complete=4 ; we have sent/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_current_block_number: .res 2
tftp_actual_server_port: .res 2 ;this is read from the reply - it is not (usually) the port # we send the RRQ or WRQ 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
@ -93,15 +93,34 @@ tftp_opcode: .res 2 ; will be set to 4 if we are doing a RRQ, or 7 if we are doi
.code
;uploads a file to a tftp server
; inputs:
; tftp_ip: ip address of host to download from (set to 255.255.255.255 for broadcast)
; tftp_filename: pointer to null terminated name of file to download
; of file should be loaded into (e.g. if downloading a C64 'prg' file)
; a callback vector should have been set with tftp_set_callback_vector
; outputs: carry flag is set if there was an error
; if a callback vector has been set with tftp_set_callback_vector
; then the specified routine will be called once for each 512 byte packet
; sent from the tftp server (each time AX will point at data block just arrived,
; and tftp_data_block_length will contain number of bytes in that data block)
; otherwise, the buffer at tftp_load_address will be filled
; with file downloaded.
; tftp_load_address: will be set to the actual address loaded into (NB - this field is
; ignored if a callback vector has been set with tftp_set_callback_vector)
tftp_upload:
ldax #$0200 ;opcode 02 = WRQ
jmp set_tftp_opcode
; query a tftp server for a directory listing (uses a non-standard tftp opcode,
; currently only supported by tftp server built in to 'netboot65' server)
; inputs:
; tftp_ip: ip address of host to download from (set to 255.255.255.255 for broadcast)
; tftp_filename: pointer to null terminated filemask (e.g. "*.prg",0)
; tftp_load_address: memory location that dir will be stored in (NB - this field is
; ignored if a callback vector has been set with tftp_set_download_callback)
; ignored if a callback vector has been set with tftp_set_callback_vector)
; outputs: carry flag is set if there was an error, clear otherwise
; if a callback vector has been set with tftp_set_download_callback
; if a callback vector has been set with tftp_set_callback_vector
; then the specified routine will be called once for each 512 byte packet
; sent from the tftp server (each time AX will point at data block just arrived,
; and tftp_data_block_length will contain number of bytes in that data block)
@ -124,27 +143,28 @@ tftp_directory_listing:
; treat first 2 bytes received from tftp server as memory address that rest
; of file should be loaded into (e.g. if downloading a C64 'prg' file)
; outputs: carry flag is set if there was an error
; if a callback vector has been set with tftp_set_download_callback
; if a callback vector has been set with tftp_set_callback_vector
; then the specified routine will be called once for each 512 byte packet
; sent from the tftp server (each time AX will point at data block just arrived,
; and tftp_data_block_length will contain number of bytes in that data block)
; otherwise, the buffer at tftp_load_address will be filled
; with file downloaded.
; tftp_load_address: will be set to the actual address loaded into (NB - this field is
; ignored if a callback vector has been set with tftp_set_download_callback)
; ignored if a callback vector has been set with tftp_set_callback_vector)
tftp_download:
ldax #$0100 ;opcode 01 = RRQ
set_tftp_opcode:
stax tftp_opcode
lda #tftp_initializing
sta tftp_state
sta tftp_expected_block_number ;(tftp_initializing=1)
ldx #00
stax tftp_current_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
inc tftp_client_port_low_byte ;each transfer uses a different client port
ldx tftp_client_port_low_byte ;so we don't get confused by late replies to a previous call
jsr udp_add_listener
@ -187,20 +207,25 @@ set_tftp_opcode:
jsr send_ack ;send the ack for the last block
lda #$69
ldx tftp_client_port_low_byte
jsr udp_remove_listener
jsr udp_remove_listener
rts
@not_complete:
cmp #tftp_receiving_file
bne @not_receiving
jsr send_ack
cmp #tftp_transmission_in_progress
bne @not_transmitting
jsr send_tftp_packet
jmp @inner_delay_loop
@not_receiving:
@not_transmitting:
jsr send_request_packet
@inner_delay_loop:
jsr ip65_process
@inner_delay_loop:
jsr ip65_process
jsr check_for_abort_key
bcc @no_abort
lda #NB65_ERROR_ABORTED_BY_USER
sta ip65_error
jmp @exit_with_error
@no_abort:
lda tftp_break_inner_loop
bne @outer_delay_loop
jsr timer_read
@ -259,7 +284,7 @@ send_request_packet:
ldax #tftp_outp
jsr udp_send
bcs @error_in_send
lda #tftp_rrq_sent
lda #tftp_initial_request_sent
sta tftp_state
rts
@error_in_send:
@ -271,8 +296,13 @@ send_request_packet:
send_ack:
ldax #$0400 ;opcode 04 = ACK
stax tftp_outp
ldx tftp_block_number_to_ack
ldx tftp_current_block_number
lda tftp_current_block_number+1
dex
stax tftp_outp+2
ldax #04
stax tftp_send_len
send_tftp_packet: ;TFTP block should be created in tftp_outp, we just add the UDP&IP stuff and send
lda #$69
ldx tftp_client_port_low_byte
stax udp_send_src_port
@ -288,10 +318,11 @@ send_ack:
ldx tftp_actual_server_port
lda tftp_actual_server_port+1
stax udp_send_dest_port
ldax #04
ldax tftp_send_len
stax udp_send_len
ldax #tftp_outp
; .byte $92
jsr udp_send
rts
@ -334,15 +365,13 @@ tftp_in:
@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
cmp tftp_current_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
;this is the block we wanted
inc tftp_current_block_number
lda #tftp_transmission_in_progress
sta tftp_state
lda #TFTP_MAX_RESENDS
sta tftp_resend_counter
@ -384,7 +413,8 @@ tftp_in:
@got_pointer_to_tftp_data:
jsr tftp_callback_vector
jsr send_ack
lda udp_inp+4 ;check the length of the UDP packet
cmp #02
bne @last_block
@ -392,7 +422,47 @@ tftp_in:
lda udp_inp+5
cmp #$0c
bne @last_block
@not_data_block:
cmp #3
beq :+
jmp @not_ack
:
;it's an ACK, so we must be sending a file
ldx tftp_inp+3 ;get the (low byte) of the data block
inx
cpx tftp_current_block_number
beq :+
jmp @not_expected_block_number
:
;the last block we sent was acked so now we need to send the next one
;
ldax #output_buffer+4
jsr tftp_callback_vector ;this (caller supplied) routine should fill the buffer with up to 512 bytes
stax tftp_data_block_length
clc
adc #4
bcc :+
inx
:
stax tftp_send_len
ldax #$0300 ;opcode 03 = DATA
stax tftp_outp
ldx tftp_current_block_number
lda tftp_current_block_number+1
stax tftp_outp+2
jsr send_tftp_packet
inc tftp_current_block_number
bcc :+
inc tftp_current_block_number+1
:
lda tftp_data_block_length+1 ;get length of data we just sent (high byte)
cmp #2
beq @last_block
@not_ack:
@not_expected_block_number:
rts

View File

@ -44,7 +44,7 @@ nb65_std_cart.bin: nb65_std_cart.o $(IP65LIB) $(C64NB65LIB) $(INCFILES) ../cfg/r
ruby fix_cart.rb $@ 8192
nb65_rrnet.bin: nb65_rrnet.o $(IP65LIB) $(C64NB65LIB) $(INCFILES) ../cfg/rrbin.cfg
$(LD) -m nb65_rrnet.map -vm -C ../cfg/rrbin.cfg -o $@ $< $(IP65LIB) $(C64NB65LIB)
$(LD) -m nb65_rrnet.map -Ln nb65_rr.lab -vm -C ../cfg/rrbin.cfg -o $@ $< $(IP65LIB) $(C64NB65LIB)
ruby fix_cart.rb $@ 8193
utherboot.pg2: utherboot.o $(IP65LIB) $(APPLE2PROGLIB) $(INCFILES) ../cfg/a2language_card.cfg

View File

@ -72,8 +72,9 @@
.import __DATA_RUN__
.import __DATA_SIZE__
.import cfg_tftp_server
tftp_dir_buffer = $6000
tftp_dir_buffer = $6020
nb65_param_buffer = $6000
.data
exit_cart:
.if (BANKSWITCH_SUPPORT=$02)
@ -90,7 +91,6 @@ call_downloaded_prg:
.bss
nb65_param_buffer: .res $20
.segment "CARTRIDGE_HEADER"
@ -161,6 +161,8 @@ ldax #init_msg
nb65call #NB65_INITIALIZE
main_menu:
lda #21 ;make sure we are in upper case
sta $d018
jsr cls
ldax #netboot65_msg
jsr print
@ -285,13 +287,14 @@ main_menu:
jsr select_option_from_menu
bcc @tftp_filename_set
lda #21 ;switch back to upper case
sta $d018
jmp main_menu
@tftp_filename_set:
jsr download
bcc @file_downloaded_ok
jmp bad_boot
@tftp_boot_failed:
jsr wait_for_keypress
jmp main_menu
@dir_failed:
ldax #tftp_dir_listing_fail_msg
@ -306,7 +309,7 @@ main_menu:
ldax #no_files_on_server
jsr print
jmp bad_boot
jmp @tftp_boot_failed
@file_downloaded_ok:

View File

@ -15,6 +15,16 @@ INCFILES=\
../inc/common.i\
../inc/commonprint.i\
../inc/net.i\
all: \
ip65test.dsk \
testdns.prg \
testdns.pg2 \
testtftp.prg \
testtftp.pg2\
test_cart_api.prg\
testdottedquad.pg2\
testdottedquad.prg\
%.o: %.c
$(CC) -c $(CFLAGS) $<
@ -28,21 +38,14 @@ INCFILES=\
%.pg2: %.o $(IP65LIB) $(APPLE2NETLIB) $(INCFILES) ../cfg/a2bin.cfg
$(LD) -C ../cfg/a2bin.cfg -o $*.pg2 $(AFLAGS) $< $(IP65LIB) $(APPLE2PROGLIB)
ip65test.dsk: testdns.pg2 testdottedquad.pg2
ip65test.dsk: testdns.pg2 testdottedquad.pg2 testtftp.pg2
ripxplore.rb --init BeautifulBoot ip65test.dsk -a testdns.pg2 -t AppleBinary
ripxplore.rb ip65test.dsk -a testtftp.pg2 -t AppleBinary
ripxplore.rb ip65test.dsk -a testdottedquad.pg2 -t AppleBinary
all: \
ip65test.dsk \
testdns.prg \
test_cart_api.prg \
testdns.pg2 \
testdottedquad.pg2 \
testdottedquad.prg \
ripxplore.rb ip65test.dsk -a testdns.pg2 -t AppleBinary
clean:
rm -f *.o
rm -f testdns.prg testdns.map testdns.pg2 testdottedquad.prg testdottedquad.pg2
rm -f *.o *.pg2 *.prg
rm -f ip65test.dsk
distclean: clean

100
client/test/testtftp.s Normal file
View File

@ -0,0 +1,100 @@
.include "../inc/common.i"
.include "../inc/commonprint.i"
.include "../inc/net.i"
.import exit_to_basic
.import cfg_get_configuration_ptr
.import copymem
.importzp copy_src
.importzp copy_dest
.import __CODE_LOAD__
.import __CODE_SIZE__
.import __RODATA_SIZE__
.import __DATA_SIZE__
.import tftp_upload
.import tftp_set_callback_vector
.import tftp_ip
.importzp tftp_filename
.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
init_ip_via_dhcp
jsr print_ip_config
ldax #upload_callback
jsr tftp_set_callback_vector
lda #0
sta block_number
ldax #test_file
stax tftp_filename
lda #$ff
ldx #$3
:
sta tftp_ip,x
dex
bpl :-
ldax #sending
jsr print
jsr tftp_upload
rts
upload_callback:
stax copy_dest
ldax #buffer1
stax copy_src
inc block_number
lda block_number
ldx #00
@next_byte:
sta buffer1,x
sta buffer2,x
inx
bne @next_byte
cmp #7
beq @last_block
ldax #512
jmp :+
@last_block:
ldax #0
:
stax block_length
jsr copymem
ldax block_length
rts
.rodata
test_file: .byte "TESTFILE.BIN",0
sending: .byte "SENDING...",0
.bss
block_number: .res 1
block_length: .res 2
buffer1: .res 256
buffer2: .res 256

1
dist/make_dist.rb vendored
View File

@ -16,6 +16,7 @@ end
[
["client/nb65/utherboot.dsk","nb65/"],
["client/nb65/nb65_rrnet.bin","nb65/"],
["client/nb65/nb65_c64_ram.prg","nb65/"],
["client/nb65/nb65_std_cart.bin","nb65/"],
["server/lib/tftp_server.rb","lib"],
["server/bin/tftp_only_server.rb","bin/tftp_server.rb"],

View File

@ -9,6 +9,8 @@
require 'socket'
class Netboot65TFTPServer
TFTP_OPCODES={
1=>'RRQ', #read request
2=>'WRQ', #write request
@ -34,7 +36,7 @@ class Netboot65TFTPServer
@bootfile_dir=bootfile_dir
@port=port
@server_thread=nil
@current_connection={}
end
def send_error(client_ip,client_port,error_code,error_msg)
@ -86,12 +88,17 @@ class Netboot65TFTPServer
client_sock=UDPSocket.open
client_sock.connect(client_ip,client_port)
client_id="#{client_ip}:#{client_port}"
if @current_connection[client_id]==true then
log_msg("already sending to #{client_id}" )
end
@current_connection[client_id]=true
log_msg("receiving #{filename} from #{client_ip}:#{client_port}")
got_last_block=false
sent_last_ack=false
finished=false
block_number=0
until sent_last_ack do
finished
until finished do
packet=[4,block_number].pack("nn")
got_block=false
TFTP_MAX_RESENDS.times do |attempt_number|
@ -99,7 +106,7 @@ class Netboot65TFTPServer
client_sock.send(packet,0,client_ip,client_port)
if got_last_block then
puts "last block received"
sent_last_ack=true
finished=true
break
else
if (IO.select([client_sock], nil, nil, 1)) then
@ -123,15 +130,17 @@ class Netboot65TFTPServer
end
end
break if got_block
if !got_block then
log_msg "TFTP: timed out waiting for DATA for block #{block_number+1} from #{client_ip}"
break
end
end
end
if !got_block && !finished then
log_msg "TFTP: timed out waiting for DATA for block #{block_number+1} from #{client_ip}"
finished=true
break
end
block_number+=1
end
file_handle.close
@current_connection[client_id]=false
end
def start()