2009-01-22 01:27:03 +00:00
;########################
; minimal tftp implementation (client only)
2009-01-26 01:54:59 +00:00
; supports file download (not upload) and custom directory listing (using opcode of 0x65)
2009-01-22 01:27:03 +00:00
; written by jonno@jamtronix.com 2009
;
;########################
2009-01-25 01:54:36 +00:00
; to get a directory listing
; 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 dir will be stored in
; set tftp_filename to null terminated filemask (e.g. "*.prg")
; then call tftp_directory_listing
; on exit: carry flag is set if there was an error.
;
; to d/l a file:
2009-01-22 01:27:03 +00:00
; 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
2009-01-25 01:54:36 +00:00
; 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)
;
2009-01-22 01:27:03 +00:00
;########################
TFTP_ M A X _ R E S E N D S =10
TFTP_ T I M E R _ M A S K = $ F 8 ;mask lower two bits, means we wait for 8 x1/4 seconds
.include " . . / inc/ c o m m o n . i "
.exportzp tftp_filename
.export tftp_load_address
.export tftp_ip
.export tftp_download
2009-01-25 01:54:36 +00:00
.export tftp_directory_listing
2009-01-22 01:27:03 +00:00
.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 " IP6 5 Z P " : z e r o p a g e
tftp_filename : .res 2
.bss
;packet offsets
tftp_ i n p = u d p _ i n p + u d p _ d a t a
tftp_outp : .res 128
;everything after filename in a request at a relative address, not fixed, so don't bother defining offset constants
tftp_ s e r v e r _ p o r t =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_ i n i t i a l i z i n g = 1 ; initial state
tftp_ r r q _ s e n t =2 ; sent the read request, waiting for some data
tftp_ r e c e i v i n g _ f i l e =3 ; we have received the first packet of file data
tftp_ c o m p l e t e =4 ; we have received the final packet of file data
tftp_ e r r o r =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
2009-01-25 01:54:36 +00:00
tftp_opcode : .res 2 ; will be set to 4 if we are doing a RRQ, or 7 if we are doing a DIR
2009-01-22 01:27:03 +00:00
.code
2009-01-25 01:54:36 +00:00
tftp_directory_listing :
2009-01-26 01:54:59 +00:00
ldax #$ 6500 ;opcode 65 = netboot65 DIR (non-standard opcode)
2009-01-25 01:54:36 +00:00
jmp s e t _ t f t p _ o p c o d e
2009-01-22 01:27:03 +00:00
2009-01-25 01:54:36 +00:00
tftp_download :
ldax #$ 0100 ;opcode 01 = RRQ
set_tftp_opcode :
stax t f t p _ o p c o d e
2009-01-22 01:27:03 +00:00
lda #t f t p _ i n i t i a l i z i n g
sta t f t p _ s t a t e
sta t f t p _ e x p e c t e d _ b l o c k _ n u m b e r ;(tftp_initializing=1)
ldax t f t p _ l o a d _ a d d r e s s
stax t f t p _ c u r r e n t _ m e m l o c
ldax #t f t p _ i n
stax u d p _ c a l l b a c k
lda #$ 69
inc t f t p _ c l i e n t _ p o r t _ l o w _ b y t e ;each call to resolve uses a different client address
ldx t f t p _ c l i e n t _ p o r t _ l o w _ b y t e ;so we don't get confused by late replies to a previous call
jsr u d p _ a d d _ l i s t e n e r
bcc : + ;bail if we couldn't listen on the port we want
rts
:
lda #T F T P _ M A X _ R E S E N D S
sta t f t p _ r e s e n d _ c o u n t e r
@outer_delay_loop:
jsr t i m e r _ r e a d
txa
and #T F T P _ T I M E R _ M A S K
sta t f t p _ t i m e r ;we only care about the high byte
lda #0
sta t f t p _ b r e a k _ i n n e r _ l o o p
lda t f t p _ s t a t e
cmp #t f t p _ i n i t i a l i z i n g
bne @not_initializing
2009-01-25 01:54:36 +00:00
jsr s e n d _ r e q u e s t _ p a c k e t
2009-01-22 01:27:03 +00:00
jmp @inner_delay_loop
@not_initializing:
cmp #t f t p _ e r r o r
bne @not_error
@exit_with_error:
lda #$ 69
ldx t f t p _ c l i e n t _ p o r t _ l o w _ b y t e
jsr u d p _ r e m o v e _ l i s t e n e r
sec
rts
@not_error:
cmp #t f t p _ c o m p l e t e
bne @not_complete
jsr s e n d _ a c k ;send the ack for the last block
lda #$ 69
ldx t f t p _ c l i e n t _ p o r t _ l o w _ b y t e
jsr u d p _ r e m o v e _ l i s t e n e r
rts
@not_complete:
cmp #t f t p _ r e c e i v i n g _ f i l e
bne @not_receiving
jsr s e n d _ a c k
jmp @inner_delay_loop
@not_receiving:
2009-01-25 01:54:36 +00:00
jsr s e n d _ r e q u e s t _ p a c k e t
2009-01-22 01:27:03 +00:00
@inner_delay_loop:
jsr i p65 _ p r o c e s s
lda t f t p _ b r e a k _ i n n e r _ l o o p
bne @outer_delay_loop
jsr t i m e r _ r e a d
txa
and #T F T P _ T I M E R _ M A S K
cmp t f t p _ t i m e r
beq @inner_delay_loop
dec t f t p _ r e s e n d _ c o u n t e r
bne @outer_delay_loop
jmp @exit_with_error
2009-01-25 01:54:36 +00:00
send_request_packet :
2009-01-22 01:27:03 +00:00
lda #t f t p _ i n i t i a l i z i n g
sta t f t p _ s t a t e
2009-01-25 01:54:36 +00:00
ldax t f t p _ o p c o d e
2009-01-22 01:27:03 +00:00
stax t f t p _ o u t p
ldx #$ 01 ;we inc x/y at start of loop, so
ldy #$ f f ; s e t t h e m t o b e 1 b e l o w w h e r e w e w a n t t h e c o p y t o b e g i n
@copy_filename_loop:
inx
iny
bmi @error_in_send ;if we get to 0x80 bytes, we've gone too far
lda ( t f t p _ f i l e n a m e ) ,y
sta t f t p _ o u t p ,x
bne @copy_filename_loop
ldy #$ f f
@copy_mode_loop:
inx
iny
lda t f t p _ o c t e t _ m o d e ,y
sta t f t p _ o u t p ,x
bne @copy_mode_loop
inx
txa
ldx #0
stax u d p _ s e n d _ l e n
lda #$ 69
ldx t f t p _ c l i e n t _ p o r t _ l o w _ b y t e
stax u d p _ s e n d _ s r c _ p o r t
ldx #3 ; set destination address
: lda t f t p _ i p ,x
sta u d p _ s e n d _ d e s t ,x
dex
bpl : -
ldax #t f t p _ s e r v e r _ p o r t ; s e t d e s t i n a t i o n p o r t
stax u d p _ s e n d _ d e s t _ p o r t
ldax #t f t p _ o u t p
jsr u d p _ s e n d
bcs @error_in_send
lda #t f t p _ r r q _ s e n t
sta t f t p _ s t a t e
rts
@error_in_send:
sec
rts
send_ack :
ldax #$ 0400 ;opcode 04 = ACK
stax t f t p _ o u t p
ldx t f t p _ b l o c k _ n u m b e r _ t o _ a c k
stax t f t p _ o u t p + 2
lda #$ 69
ldx t f t p _ c l i e n t _ p o r t _ l o w _ b y t e
stax u d p _ s e n d _ s r c _ p o r t
lda t f t p _ a c t u a l _ s e r v e r _ i p
sta u d p _ s e n d _ d e s t
lda t f t p _ a c t u a l _ s e r v e r _ i p + 1
sta u d p _ s e n d _ d e s t + 1
lda t f t p _ a c t u a l _ s e r v e r _ i p + 2
sta u d p _ s e n d _ d e s t + 2
lda t f t p _ a c t u a l _ s e r v e r _ i p + 3
sta u d p _ s e n d _ d e s t + 3
ldx t f t p _ a c t u a l _ s e r v e r _ p o r t
lda t f t p _ a c t u a l _ s e r v e r _ p o r t + 1
stax u d p _ s e n d _ d e s t _ p o r t
ldax #04
stax u d p _ s e n d _ l e n
ldax #t f t p _ o u t p
jsr u d p _ s e n d
rts
tftp_in :
lda t f t p _ i n p + 1 ;get the opcode
cmp #5
bne @not_an_error
@recv_error:
lda #t f t p _ e r r o r
sta t f t p _ s t a t e
rts
@not_an_error:
cmp #3
beq : +
jmp @not_data_block
:
lda #0
sta t f t p _ j u s t _ s e t _ n e w _ l o a d _ a d d r e s s ;clear the flag
clc
lda t f t p _ l o a d _ a d d r e s s
adc t f t p _ l o a d _ a d d r e s s + 1 ;is load address currently $0000?
bne @dont_set_load_address
ldax u d p _ i n p + $ 0 c ;get first two bytes of data
stax t f t p _ l o a d _ a d d r e s s ;make them the new load adress
stax t f t p _ c u r r e n t _ m e m l o c ;also the current memory destination
lda #1 ;set the flag
sta t f t p _ j u s t _ s e t _ n e w _ l o a d _ a d d r e s s
@dont_set_load_address:
lda t f t p _ i n p + 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 t f t p _ e x p e c t e d _ b l o c k _ n u m b e r
beq : +
jmp @not_expected_block_number
:
;this is the block we wanted
sta t f t p _ b l o c k _ n u m b e r _ t o _ a c k
inc t f t p _ e x p e c t e d _ b l o c k _ n u m b e r
lda #t f t p _ r e c e i v i n g _ f i l e
sta t f t p _ s t a t e
lda #T F T P _ M A X _ R E S E N D S
sta t f t p _ r e s e n d _ c o u n t e r
lda #1
sta t f t p _ b r e a k _ i n n e r _ l o o p
ldax u d p _ i n p + u d p _ s r c _ p o r t
stax t f t p _ a c t u a l _ s e r v e r _ p o r t
ldax i p _ i n p + i p _ s r c
stax t f t p _ a c t u a l _ s e r v e r _ i p
ldax i p _ i n p + i p _ s r c + 2
stax t f t p _ a c t u a l _ s e r v e r _ i p + 2
lda t f t p _ j u s t _ s e t _ n e w _ l o a d _ a d d r e s s
bne @skip_first_2_bytes_in_calculating_header_length
lda u d p _ i n p + 5 ;get the low byte of udp packet length
sec
sbc #$ 0 c ;take off the length of the UDP header+OPCODE + BLOCK
jmp @adjusted_header_length
@skip_first_2_bytes_in_calculating_header_length:
lda u d p _ i n p + 5 ;get the low byte of udp packet length
sec
sbc #$ 0 e ;take off the length of the UDP header+OPCODE + BLOCK + first 2 bytes (memory location)
@adjusted_header_length:
sta t f t p _ b y t e s _ t o _ c o p y
lda u d p _ i n p + 4 ;get high byte of the length of the UDP packet
sbc #0
sta t f t p _ b y t e s _ t o _ c o p y + 1
lda t f t p _ j u s t _ s e t _ n e w _ l o a d _ a d d r e s s
bne @skip_first_2_bytes_in_calculating_copy_src
ldax #u d p _ i n p + $ 0 c
jmp @got_copy_src
@skip_first_2_bytes_in_calculating_copy_src:
ldax #u d p _ i n p + $ 0 e
@got_copy_src:
stax c o p y _ s r c
ldax t f t p _ c u r r e n t _ m e m l o c
stax c o p y _ d e s t
ldax t f t p _ b y t e s _ t o _ c o p y
jsr c o p y m e m
clc
lda t f t p _ b y t e s _ t o _ c o p y ;update the location where the next data will go
adc t f t p _ c u r r e n t _ m e m l o c
sta t f t p _ c u r r e n t _ m e m l o c
lda t f t p _ b y t e s _ t o _ c o p y + 1
adc t f t p _ c u r r e n t _ m e m l o c + 1
sta t f t p _ c u r r e n t _ m e m l o c + 1
lda u d p _ i n p + 4 ;check the length of the UDP packet
cmp #02
bne @last_block
lda u d p _ i n p + 5
cmp #$ 0 c
bne @last_block
@not_data_block:
@not_expected_block_number:
rts
@last_block:
lda #t f t p _ c o m p l e t e
sta t f t p _ s t a t e
rts
.rodata
tftp_octet_mode : .asciiz " OCTET "