ip65 technical reference
File : ip65/tftp.s
minimal tftp implementation (client only)
supports file upload and download
functions
function | description |
---|
tftp_callback_vector | |
tftp_clear_callbacks | clear callback vectors, i.e. all future transfers read from/write to RAM
inputs: none
outputs: none
|
tftp_download | download a file from 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
tftp_load_address: memory location that dir will be stored in, or $0000 to
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_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_set_callback_vector | set up vector of routine to be called when each 512 packet arrives from tftp server
when downloading OR for routine to be called when ready to send new block
when uploading.
when vector is called when downloading, AX will point to data that was downloaded,
tftp_data_block_length will be set to length of downloaded data block. This will be
equal to $200 (512) for each block EXCEPT the final block. THe final block will
always be less than $200 bytes - if the file is an exact multiple if $200 bytes
long, then a final block will be received with length $00.
when vector is called when uploading, AX will point to a 512 byte buffer that
should be filled with the next block. the user supplied routine should set AX
to be equal to the actual number of bytes inserted into the buffer, which should
equal to $200 (512) for each block EXCEPT the final block. The final block must
always be less than $200 bytes - if the file is an exact multiple if $200 bytes
long, then a final block must be created with length $00.
inputs:
AX - address of routine to call for each packet.
outputs: none
|
tftp_upload | uploads a file to a tftp server with data retrieved from user supplied routine
inputs:
tftp_ip: ip address of host to send file to (set to 255.255.255.255 for broadcast)
tftp_filename: pointer to null terminated name of file to upload
a callback vector should have been set with tftp_set_callback_vector
outputs: carry flag is set if there was an error
the specified routine will be called once for each 512 byte packet
to be sent from the tftp server.
|
tftp_upload_from_memory | uploads a file to a tftp server with data retrieved from specified memory location
inputs:
tftp_ip: ip address of host to send file to (set to 255.255.255.255 for broadcast)
tftp_filename: pointer to null terminated name of file to upload
tftp_load_address: starting address of data to be sent
tftp_filesize: length of data to send
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
to be sent to the tftp server
|
variables
variable | description | size (bytes) |
---|
tftp_data_block_length | | 2 |
tftp_filename | name of file to d/l or filemask to get directory listing for
| 2 |
tftp_filesize | will be set by tftp_download, needs to be set before calling tftp_upload_from_memory
| 2 |
tftp_ip | ip address of tftp server - set to 255.255.255.255 (broadcast) to send request to all tftp servers on local lan
| 4 |
tftp_load_address | address file will be (or was) downloaded to
| 2 |
implementation
;minimal tftp implementation (client only)
;supports file upload and download
TFTP_MAX_RESENDS=10
TFTP_TIMER_MASK=$F8 ;mask lower two bits, means we wait for 8 x1/4 seconds
.include "../inc/common.i"
.ifndef KPR_API_VERSION_NUMBER
.define EQU =
.include "../inc/kipper_constants.i"
.endif
.exportzp tftp_filename
.export tftp_load_address
.export tftp_ip
.export tftp_download
.export tftp_upload
.export tftp_data_block_length
.export tftp_set_callback_vector
.export tftp_callback_vector
.export tftp_clear_callbacks
.export tftp_filesize
.export tftp_upload_from_memory
.import ip65_process
.import ip65_error
.import udp_add_listener
.import udp_remove_listener
.import output_buffer
.import udp_callback
.import udp_send
.import check_for_abort_key
.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 ;name of file to d/l or filemask to get directory listing for
.bss
;packet offsets
tftp_inp = udp_inp + udp_data
tftp_outp = output_buffer
;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 ;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_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_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
tftp_opcode: .res 2 ; will be set to 4 if we are doing a RRQ, or 7 if we are doing a DIR
tftp_filesize: .res 2 ;will be set by tftp_download, needs to be set before calling tftp_upload_from_memory
tftp_bytes_remaining: .res 2
.code
;uploads a file to a tftp server with data retrieved from specified memory location
; inputs:
; tftp_ip: ip address of host to send file to (set to 255.255.255.255 for broadcast)
; tftp_filename: pointer to null terminated name of file to upload
; tftp_load_address: starting address of data to be sent
; tftp_filesize: length of data to send
; 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
; to be sent to the tftp server
tftp_upload_from_memory:
ldax #copy_ram_to_tftp_block
jsr tftp_set_callback_vector
ldax tftp_filesize
stax tftp_bytes_remaining
lda #00
sta tftp_filesize
sta tftp_filesize+1
;uploads a file to a tftp server with data retrieved from user supplied routine
; inputs:
; tftp_ip: ip address of host to send file to (set to 255.255.255.255 for broadcast)
; tftp_filename: pointer to null terminated name of file to upload
; a callback vector should have been set with tftp_set_callback_vector
; outputs: carry flag is set if there was an error
; the specified routine will be called once for each 512 byte packet
; to be sent from the tftp server.
tftp_upload:
ldax #$0200 ;opcode 02 = WRQ
jmp set_tftp_opcode
;download a file from 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
; tftp_load_address: memory location that dir will be stored in, or $0000 to
; 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_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_download:
lda #00
sta tftp_filesize
sta tftp_filesize+1
ldx #$01 ;opcode 01 = RRQ (A should already be zero from having just reset file length)
set_tftp_opcode:
stax tftp_opcode
lda #tftp_initializing
sta tftp_state
ldax #0000
stax tftp_current_block_number
ldax tftp_load_address
stax tftp_current_memloc
ldax #tftp_in
stax udp_callback
ldx #$69
inc tftp_client_port_low_byte ;each transfer uses a different client port
lda 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
lda #KPR_ERROR_PORT_IN_USE
sta ip65_error
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_request_packet
jmp @inner_delay_loop
@not_initializing:
cmp #tftp_error
bne @not_error
@exit_with_error:
ldx #$69
lda 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
bcs @not_complete ;if we couldn't send the ACK (e.g. coz we need to do an ARP request) then keep looping
ldx #$69
lda tftp_client_port_low_byte
jsr udp_remove_listener
rts
@not_complete:
cmp #tftp_transmission_in_progress
bne @not_transmitting
jsr send_tftp_packet
jmp @inner_delay_loop
@not_transmitting:
jsr send_request_packet
@inner_delay_loop:
jsr ip65_process
jsr check_for_abort_key
bcc @no_abort
lda #KPR_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
txa
and #TFTP_TIMER_MASK
cmp tftp_timer
beq @inner_delay_loop
dec tftp_resend_counter
bne @outer_delay_loop
lda #KPR_ERROR_TIMEOUT_ON_RECEIVE
sta ip65_error
jmp @exit_with_error
send_request_packet:
lda #tftp_initializing
sta tftp_state
ldax tftp_opcode
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
ldx #$69
lda 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_initial_request_sent
sta tftp_state
rts
@error_in_send:
lda #KPR_ERROR_TRANSMIT_FAILED
sta ip65_error
sec
rts
send_ack:
ldax #$0400 ;opcode 04 = ACK
stax tftp_outp
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
ldx tftp_current_block_number
lda tftp_current_block_number+1
stax tftp_outp+2
ldx #$69
lda 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 tftp_send_len
stax udp_send_len
ldax #tftp_outp
jsr udp_send
rts
got_expected_block:
lda tftp_current_block_number
inc tftp_current_block_number
bne :+
inc tftp_current_block_number+1
:
lda #tftp_transmission_in_progress
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
rts
tftp_in:
lda tftp_inp+1 ;get the opcode
cmp #5
bne @not_an_error
@recv_error:
lda #tftp_error
sta tftp_state
lda #KPR_ERROR_TRANSMISSION_REJECTED_BY_PEER
sta ip65_error
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
lda tftp_callback_address_set ;have we overridden the default handler?
bne @dont_set_load_address ;if so, don't skip the first two bytes in the file
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:
ldx tftp_inp+3 ;get the (low byte) of the data block
dex
cpx tftp_current_block_number
beq :+
jmp @not_expected_block_number
:
;this is the block we wanted
jsr got_expected_block
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_data_block_length
lda udp_inp+4 ;get high byte of the length of the UDP packet
sbc #0
sta tftp_data_block_length+1
lda tftp_just_set_new_load_address
bne @skip_first_2_bytes_in_calculating_copy_src
ldax #udp_inp+$0c
jmp @got_pointer_to_tftp_data
@skip_first_2_bytes_in_calculating_copy_src:
ldax #udp_inp+$0e
@got_pointer_to_tftp_data:
stax copy_src
ldax #output_buffer+2
stax copy_dest
ldax tftp_data_block_length
stax output_buffer
jsr copymem
ldax #output_buffer
jsr tftp_callback_vector
jsr send_ack
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
beq @not_last_block
@not_data_block:
cmp #4 ;ACK is opcode 4
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
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
jsr got_expected_block
jsr send_tftp_packet
lda tftp_data_block_length+1 ;get length of data we just sent (high byte)
cmp #2
bne @last_block
@not_last_block:
inc tftp_filesize+1 ;add $200 to file size
inc tftp_filesize+1 ;add $200 to file size
@not_ack:
@not_expected_block_number:
rts
@last_block:
lda tftp_data_block_length
sta tftp_filesize; this must be the first block that is not a multiple of 512, hence till now the low byte in tftp_filesize is still $00
lda tftp_data_block_length+1 ;this can only be 0 or 1
beq :+
inc tftp_filesize+1
:
lda #tftp_complete
sta tftp_state
rts
;default handler when block arrives:
;copy to RAM
;assumes tftp_data_block_length has been set, and AX should point to start of data
copy_tftp_block_to_ram:
clc
adc #02 ;skip the 2 byte length at start of buffer
bcc :+
inx
:
stax copy_src
ldax tftp_current_memloc
stax copy_dest
ldax tftp_data_block_length
jsr copymem
clc
lda tftp_data_block_length ;update the location where the next data will go
adc tftp_current_memloc
sta tftp_current_memloc
lda tftp_data_block_length+1
adc tftp_current_memloc+1
sta tftp_current_memloc+1
rts
;default handler for uploading a file
copy_ram_to_tftp_block:
stax copy_dest
ldax tftp_current_memloc
stax copy_src
clc
lda tftp_bytes_remaining+1
beq @last_block
cmp #01
beq @last_block
dec tftp_bytes_remaining+1
dec tftp_bytes_remaining+1
ldax #$0200
@length_is_set:
stax tftp_data_block_length
jsr copymem
inc tftp_current_memloc+1
inc tftp_current_memloc+1
ldax tftp_data_block_length
clc
rts
@last_block:
ldax tftp_bytes_remaining
jmp @length_is_set
;set up vector of routine to be called when each 512 packet arrives from tftp server
;when downloading OR for routine to be called when ready to send new block
;when uploading.
;when vector is called when downloading, AX will point to data that was downloaded,
;tftp_data_block_length will be set to length of downloaded data block. This will be
;equal to $200 (512) for each block EXCEPT the final block. THe final block will
;always be less than $200 bytes - if the file is an exact multiple if $200 bytes
;long, then a final block will be received with length $00.
;when vector is called when uploading, AX will point to a 512 byte buffer that
;should be filled with the next block. the user supplied routine should set AX
;to be equal to the actual number of bytes inserted into the buffer, which should
;equal to $200 (512) for each block EXCEPT the final block. The final block must
;always be less than $200 bytes - if the file is an exact multiple if $200 bytes
;long, then a final block must be created with length $00.
; inputs:
; AX - address of routine to call for each packet.
; outputs: none
tftp_set_callback_vector:
stax tftp_callback_vector+1
inc tftp_callback_address_set
rts
;clear callback vectors, i.e. all future transfers read from/write to RAM
;inputs: none
;outputs: none
tftp_clear_callbacks:
lda #0
sta tftp_callback_address_set
ldax #copy_tftp_block_to_ram
jmp tftp_set_callback_vector
.rodata
tftp_octet_mode: .asciiz "OCTET"
.data
tftp_callback_vector:
jmp copy_tftp_block_to_ram ;vector for action to take when a data block received (default is to store block in RAM)
tftp_callback_address_set: .byte 0
;-- LICENSE FOR tftp.s --
; The contents of this file are subject to the Mozilla Public License
; Version 1.1 (the "License"); you may not use this file except in
; compliance with the License. You may obtain a copy of the License at
; http://www.mozilla.org/MPL/
;
; Software distributed under the License is distributed on an "AS IS"
; basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
; License for the specific language governing rights and limitations
; under the License.
;
; The Original Code is ip65.
;
; The Initial Developer of the Original Code is Jonno Downes,
; jonno@jamtronix.com.
; Portions created by the Initial Developer are Copyright (C) 2009
; Jonno Downes. All Rights Reserved.
; -- LICENSE END --