; (c)opyright 2014-2017 ; Sven Oliver ("SvOlli") Moll ; and Individual Computers .include "rrnetmk3.inc" IP_PACKAGE_TFTP_OPCODE := IP_PACKAGE_UDP_PAYLOAD + $00 IP_PACKAGE_TFTP_ERROR_CODE := IP_PACKAGE_UDP_PAYLOAD + $02 IP_PACKAGE_TFTP_ERROR_MESSAGE := IP_PACKAGE_UDP_PAYLOAD + $04 IP_PACKAGE_TFTP_FILENAME_START := IP_PACKAGE_UDP_PAYLOAD + $02 IP_PACKAGE_TFTP_DATA_BLOCKNUM := IP_PACKAGE_UDP_PAYLOAD + $02 IP_PACKAGE_TFTP_DATA_PAYLOAD := IP_PACKAGE_UDP_PAYLOAD + $04 TFTP_OPCODE_RRQ := $01 TFTP_OPCODE_WRQ := $02 TFTP_OPCODE_DATA := $03 TFTP_OPCODE_ACK := $04 TFTP_OPCODE_ERROR := $05 .segment "CODE" tftp_request_file: lda #TRANSFER_MODE_TFTP sta TRANSFER_MODE ldx #$03 : lda IP_PACKAGE_DHCP_NEXT_IP,x sta IP_HEADER_DEST_IP_ADDR,x lda RAM_IP_ADDRESS,x sta IP_HEADER_SRC_IP_ADDR,x dex bpl :- .if 0 ldx #$00 .else inx .endif stx IP_PACKAGE_TFTP_OPCODE+0 stx IP_PACKAGE_UDP_SRC_PORT+0 stx IP_PACKAGE_UDP_DEST_PORT+0 stx IP_PACKAGE_UDP_LENGTH+0 lda #$01 sta TFTP_EXPECTED_BLOCKNUM lda #TFTP_TRANSFER_PORT sta IP_PACKAGE_UDP_SRC_PORT+1 lda #TFTP_SERVER_PORT sta IP_PACKAGE_UDP_DEST_PORT+1 lda #TFTP_OPCODE_RRQ sta IP_PACKAGE_TFTP_OPCODE+1 dex ;ldx #$ff : inx lda IP_PACKAGE_DHCP_BOOT_FILENAME,x sta IP_PACKAGE_TFTP_FILENAME_START,x bne :- ldy #$ff : iny inx lda @tftp_octet,y sta IP_PACKAGE_TFTP_FILENAME_START,x bne :- jsr area_set_start txa sec ; add one extra for terminating $00 adc #IP_PACKAGE_TFTP_FILENAME_START bcc :+ inx : stx AREA_END+1 ldx #$05 : ; using ethernet broadcast is not nice, ; but it's overkill to implement arp for only one udp package lda #$ff sta ETHER_FRAME_DEST_MAC,x lda ROM_MAC_ADDR,x sta ETHER_FRAME_SRC_MAC,x dex bpl :- jsr udp_set_package_sizes jsr udp_send_package jmp receive_loop .segment "GAPDATA" @tftp_octet: .byte $6f,$63,$74,$65,$74,$00 .segment "CODE" handle_tftp_command: ; check if mode is unset lda TRANSFER_MODE bne @check_mode_tftp ; yes, set mode to tftp jsr save_remote_ip_and_mac lda #TRANSFER_MODE_TFTP sta TRANSFER_MODE @check_mode_tftp: ; check if mode is tftp cmp #TRANSFER_MODE_TFTP bne @exit ; calculating and verifying UDP checksum here is tricky, because number ; of bytes can be odd, and padding would add one byte to be copied to RAM lda AREA_END+0 pha jsr area_align_end_16 jsr calculate_udp_checksum pla sta AREA_END+0 lda #$ff cmp TEMP_A_16+0 bne @exit cmp TEMP_A_16+1 bne @exit lda IP_PACKAGE_TFTP_OPCODE+0 bne tftp_send_error ; all tftp opcodes have a 0 hibyte lda IP_PACKAGE_TFTP_OPCODE+1 cmp #TFTP_OPCODE_WRQ beq tftp_opcode_wrq jmp tftp_send_error @exit: inc $d020 jmp receive_loop tftp_opcode_wrq: lda TFTP_EXPECTED_BLOCKNUM bne @intransfer ;lda #$00 ; $00 because of bne sta TFTP_LOAD_ADDRESS+0 sta TFTP_LOAD_ADDRESS+1 lda #$01 sta TFTP_EXPECTED_BLOCKNUM jsr tftp_send_ack_0 @intransfer: jmp receive_loop tftp_send_error: jsr recall_cancel lda #<(IP_PACKAGE_TFTP_OPCODE+@tftp_error_message_end-@tftp_error_message_start) sta AREA_END+0 lda #>(IP_PACKAGE_TFTP_OPCODE+@tftp_error_message_end-@tftp_error_message_start) sta AREA_END+1 ldx #(@tftp_error_message_end-@tftp_error_message_start-1) : lda @tftp_error_message_start,x sta IP_PACKAGE_TFTP_OPCODE,x dex bpl :- lda IP_PACKAGE_UDP_SRC_PORT+0 sta IP_PACKAGE_UDP_DEST_PORT+0 lda IP_PACKAGE_UDP_SRC_PORT+1 sta IP_PACKAGE_UDP_DEST_PORT+1 lda #>TFTP_SERVER_PORT sta IP_PACKAGE_UDP_SRC_PORT+0 lda #TFTP_OPCODE_ERROR,e: something went wrong ; shortcut: since a data block is 512 bytes in size, and there is a ; maximum of 62k ram to fill, there can't be a blocknum > 124, so ; hibyte must always be 0 lda IP_PACKAGE_TFTP_DATA_BLOCKNUM+0 beq @lobyte @error: jmp tftp_internal_error @lobyte: lda IP_PACKAGE_TFTP_DATA_BLOCKNUM+1 cmp TFTP_EXPECTED_BLOCKNUM beq @ok bcs @error ; number is smaller, just a duplicate of data we already have ; so all we need to do is to send and ack for that package again jmp tftp_send_ack @ok: inc TFTP_EXPECTED_BLOCKNUM lda #IP_PACKAGE_TFTP_DATA_PAYLOAD sta AREA_START+1 jsr area_set_end_udp ; store MAC address if not done so already ; this is a workaround for the conrner-case if the request package ; was sent via ARP broadcast ldx #$05 lda #$00 : ora MAC_ADDRESS_REMOTE,x dex bpl :- tax bne :+ jsr save_remote_ip_and_mac : ; check the rare case that last package contains no payload jsr area_check_end_16 beq @lastpackage lda TFTP_LOAD_ADDRESS+0 ora TFTP_LOAD_ADDRESS+1 bne @got_load_address .if USE_COMPACT_CODE jsr advance_area_start_by2 .else ; this should not trigger a page wrap inc AREA_START+0 inc AREA_START+0 .endif lda IP_PACKAGE_TFTP_DATA_PAYLOAD+0 sta TFTP_LOAD_ADDRESS+0 sta TEMP_B_16+0 ldx IP_PACKAGE_TFTP_DATA_PAYLOAD+1 stx TFTP_LOAD_ADDRESS+1 stx TEMP_B_16+1 @got_load_address: jsr current_address_out sei lda #$33 sta $01 @copyloop: ldy #$00 lda (AREA_START),y sta (TEMP_B_16),y inc TEMP_B_16+0 bne :+ inc TEMP_B_16+1 : .if USE_COMPACT_CODE jsr advance_area_start jsr area_check_end_16 .else inc AREA_START+0 bne :+ inc AREA_START+1 : lda AREA_START+0 cmp AREA_END+0 bne @copyloop lda AREA_START+1 cmp AREA_END+1 .endif bne @copyloop lda #$37 sta $01 cli lda #$02 cmp IP_PACKAGE_UDP_LENGTH+0 bne @lastpackage lda #$0c cmp IP_PACKAGE_UDP_LENGTH+1 bne @lastpackage jsr tftp_send_ack jmp receive_loop @lastpackage: jsr tftp_send_ack lda #$00 ; for later use reset counter to handle another file sta TFTP_EXPECTED_BLOCKNUM lda TEMP_B_16+0 sta LOAD_VEC+0 lda TEMP_B_16+1 sta LOAD_VEC+1 lda TFTP_LOAD_ADDRESS+0 ldx TFTP_LOAD_ADDRESS+1 cmp #$01 bne @tftp_jump cpx #$08 bne @tftp_jump jmp disable_and_run @tftp_jump: sta TEMP_B_16+0 stx TEMP_B_16+1 jmp disable_and_jump tftp_send_ack_0: lda #$00 sta IP_PACKAGE_TFTP_DATA_BLOCKNUM+0 sta IP_PACKAGE_TFTP_DATA_BLOCKNUM+1 tftp_send_ack: lda #IP_PACKAGE_TFTP_DATA_PAYLOAD sta AREA_END+1 lda IP_PACKAGE_UDP_SRC_PORT+0 sta IP_PACKAGE_UDP_DEST_PORT+0 lda IP_PACKAGE_UDP_SRC_PORT+1 sta IP_PACKAGE_UDP_DEST_PORT+1 lda #>TFTP_TRANSFER_PORT sta IP_PACKAGE_UDP_SRC_PORT+0 lda #TFTP_OPCODE_ACK sta IP_PACKAGE_TFTP_OPCODE+0 lda #tftp_send_ack jsr recall_after_2s jsr transfer_ip_reply jsr area_set_start jmp udp_send_package tftp_internal_error: jsr clear_output jsr print .byte $02,$11,$01,"tftp INTERNAL ERROR:" .byte $02,$12,$01,"GOT AN UNEXPECTED PACKAGE" .byte $00 jmp error_wait_for_key tftp_remote_error: jsr clear_output jsr print .byte $02,$11,$01,"tftp REMOTE ERROR:" .byte $00 lda IP_PACKAGE_TFTP_ERROR_CODE+0 ldx IP_PACKAGE_TFTP_ERROR_CODE+1 jsr AXOUT jsr print .byte $02,$12,$01,"mESSAGE:",$0d .byte $00 .if 0 ; TODO: convert to printascii subroutine ldx #$00 @loop: lda IP_PACKAGE_TFTP_ERROR_MESSAGE,x jsr petscii_to_ascii beq @end pha and #$7f cmp #$20 pla ; skip unprintable chars bcc :+ ; like CLR/HOME, which would destroy databuffer jsr BSOUT : inx bne @loop @end: .else lda #IP_PACKAGE_TFTP_ERROR_MESSAGE jsr printascii .endif ; slip through error_wait_for_key: lda #$02 sta $d020 jsr print .byte $0e .byte $02,$16,$01 .byte "pRESS ",$12," space ",$92," TO RETURN TO SERVER MODE" .byte $02,$17,$01 .byte "pRESS ",$12," run/stop ",$92," TO ABORT." .byte $00 ; sei lda #$7F sta CIA1_BASE+CIA_PRA : lda CIA1_BASE+CIA_PRB cmp #$7F beq :+ cmp #$EF bne :- lda #$FF sta CIA1_BASE+CIA_PRA lda #$06 sta $d020 jmp restart_server : lda #$FF cmp CIA1_BASE+CIA_PRB bne :- sta CIA1_BASE+CIA_PRA jmp disable_and_basic .import IP_PACKAGE_DHCP_NEXT_IP .import IP_PACKAGE_DHCP_BOOT_FILENAME tftp_replace_filename: lda #$00 sta FILENAME_CHECKSUM ; make sure filename has a 0-byte at end tax : lda FILENAME_BUFFER,x jsr petscii_to_ascii sta IP_PACKAGE_DHCP_BOOT_FILENAME,x lda #$00 sta FILENAME_BUFFER,x ; clear to prevent loop in error case inx bpl :- rts