From 3ccc1865a506712814938198e88c3a0d70e95797 Mon Sep 17 00:00:00 2001 From: jonnosan Date: Thu, 16 Apr 2009 05:55:32 +0000 Subject: [PATCH] git-svn-id: http://svn.code.sf.net/p/netboot65/code@111 93682198-c243-4bdb-bd91-e943c89aac3b --- client/cfg/rrbin.cfg | 2 +- client/drivers/c64inputs.s | 19 ++--- client/ip65/output_buffer.s | 4 +- client/ip65/tftp.s | 138 +++++++++++++++++++++++++++--------- client/nb65/Makefile | 2 +- client/nb65/nb65_c64.s | 17 +++-- client/test/Makefile | 27 +++---- client/test/testtftp.s | 100 ++++++++++++++++++++++++++ dist/make_dist.rb | 1 + server/lib/tftp_server.rb | 27 ++++--- 10 files changed, 263 insertions(+), 74 deletions(-) create mode 100644 client/test/testtftp.s diff --git a/client/cfg/rrbin.cfg b/client/cfg/rrbin.cfg index d5f6945..45b9dc5 100644 --- a/client/cfg/rrbin.cfg +++ b/client/cfg/rrbin.cfg @@ -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; } diff --git a/client/drivers/c64inputs.s b/client/drivers/c64inputs.s index 6a478c7..4c59e1b 100644 --- a/client/drivers/c64inputs.s +++ b/client/drivers/c64inputs.s @@ -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 ;====================================================================== diff --git a/client/ip65/output_buffer.s b/client/ip65/output_buffer.s index d43b387..0925542 100644 --- a/client/ip65/output_buffer.s +++ b/client/ip65/output_buffer.s @@ -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 \ No newline at end of file +output_buffer: .res 520 \ No newline at end of file diff --git a/client/ip65/tftp.s b/client/ip65/tftp.s index 9b395b1..8d0f134 100644 --- a/client/ip65/tftp.s +++ b/client/ip65/tftp.s @@ -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 diff --git a/client/nb65/Makefile b/client/nb65/Makefile index 30845f5..cde7fa0 100644 --- a/client/nb65/Makefile +++ b/client/nb65/Makefile @@ -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 diff --git a/client/nb65/nb65_c64.s b/client/nb65/nb65_c64.s index bf92373..c3fe764 100644 --- a/client/nb65/nb65_c64.s +++ b/client/nb65/nb65_c64.s @@ -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: diff --git a/client/test/Makefile b/client/test/Makefile index dffa7f4..cfd6814 100644 --- a/client/test/Makefile +++ b/client/test/Makefile @@ -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 diff --git a/client/test/testtftp.s b/client/test/testtftp.s new file mode 100644 index 0000000..6eed63b --- /dev/null +++ b/client/test/testtftp.s @@ -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 \ No newline at end of file diff --git a/dist/make_dist.rb b/dist/make_dist.rb index f948d67..7131cea 100644 --- a/dist/make_dist.rb +++ b/dist/make_dist.rb @@ -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"], diff --git a/server/lib/tftp_server.rb b/server/lib/tftp_server.rb index 86355d1..dd9efa1 100644 --- a/server/lib/tftp_server.rb +++ b/server/lib/tftp_server.rb @@ -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()