From 945ace04a4152ee691dbcc90104339536b5511ca Mon Sep 17 00:00:00 2001 From: jonnosan Date: Fri, 17 Apr 2009 01:50:21 +0000 Subject: [PATCH] git-svn-id: http://svn.code.sf.net/p/netboot65/code@116 93682198-c243-4bdb-bd91-e943c89aac3b --- client/inc/common.i | 12 ++++ client/inc/nb65_constants.i | 4 +- client/ip65/function_dispatcher.s | 82 +++++++++++++++------------ client/ip65/tftp.s | 33 +++++++++-- client/test/test_cart_api.s | 13 ++++- doc/nb65_api_technical_reference.doc | Bin 107008 -> 110080 bytes 6 files changed, 100 insertions(+), 44 deletions(-) diff --git a/client/inc/common.i b/client/inc/common.i index 56c7202..277887a 100644 --- a/client/inc/common.i +++ b/client/inc/common.i @@ -15,3 +15,15 @@ stx 1+(arg) .endmacro + +.macro phax + pha + txa + pha +.endmacro + +.macro plax + pla + tax + pla +.endmacro \ No newline at end of file diff --git a/client/inc/nb65_constants.i b/client/inc/nb65_constants.i index 1e813e6..a4e3434 100644 --- a/client/inc/nb65_constants.i +++ b/client/inc/nb65_constants.i @@ -35,7 +35,8 @@ NB65_TFTP_DIRECTORY_LISTING EQU $20 ;inputs: AX points to a TFTP parameter st NB65_TFTP_DOWNLOAD EQU $21 ;inputs: AX points to a TFTP parameter structure, outputs: TFTP param structure updated with ;NB65_TFTP_POINTER updated to reflect actual load address (if load address $0000 originally passed in) NB65_TFTP_CALLBACK_DOWNLOAD EQU $22 ;inputs: AX points to a TFTP parameter structure, outputs: none -NB65_TFTP_CALLBACK_UPLOAD EQU $23 ;upload: AX points to a TFTP parameter structure, outputs: none +NB65_TFTP_UPLOAD EQU $23 ;upload: AX points to a TFTP parameter structure, outputs: none +NB65_TFTP_CALLBACK_UPLOAD EQU $24 ;upload: AX points to a TFTP parameter structure, outputs: none NB65_DNS_RESOLVE EQU $30 ;inputs: AX points to a DNS parameter structure, outputs: DNS param structure updated with ;NB65_DNS_HOSTNAME_IP updated with IP address corresponding to hostname. @@ -63,6 +64,7 @@ NB65_DRIVER_NAME EQU $1A ;2 byte pointer to name of driver NB65_TFTP_IP EQU $00 ;4 byte IP address of TFTP server NB65_TFTP_FILENAME EQU $04 ;2 byte pointer to asciiz filename (or filemask in case of NB65_TFTP_DIRECTORY_LISTING) NB65_TFTP_POINTER EQU $06 ;2 byte pointer to memory location data to be stored in OR address of callback function +NB65_TFTP_FILESIZE EQU $08 ;2 byte file length (filled in by NB65_TFTP_DOWNLOAD, must be passed in to NB65_TFTP_UPLOAD) ;offsets in DNS parameter structure (used by NB65_DNS_RESOLVE) NB65_DNS_HOSTNAME EQU $00 ;2 byte pointer to asciiz hostname to resolve (can also be a dotted quad string) diff --git a/client/ip65/function_dispatcher.s b/client/ip65/function_dispatcher.s index 9fc1df4..22a2a36 100644 --- a/client/ip65/function_dispatcher.s +++ b/client/ip65/function_dispatcher.s @@ -23,6 +23,7 @@ .import tftp_download .import tftp_upload .import tftp_set_callback_vector +.import tftp_filesize .import dns_ip .import dns_resolve .import dns_set_hostname @@ -46,7 +47,7 @@ nb65_params = copy_src .data -old_ax: .res 2 + jmp_old_irq: jmp $0000 @@ -102,7 +103,6 @@ set_tftp_callback_vector: nb65_dispatcher: stax nb65_params - stax old_ax cpy #NB65_INITIALIZE @@ -132,37 +132,6 @@ irq_handler_installed: : - - cpy #NB65_TFTP_DIRECTORY_LISTING - bne :+ - jsr set_tftp_params - jsr tftp_directory_listing - -@after_tftp_call: ;write the current load address back to the param buffer (so if $0000 was passed in, the caller can find out the actual value used) - bcs @tftp_error - ldax old_ax - stax nb65_params - - ldy #NB65_TFTP_POINTER - lda tftp_load_address - sta (nb65_params),y - iny - lda tftp_load_address+1 - sta (nb65_params),y - - clc -@tftp_error: -@dns_error: - rts -: - - cpy #NB65_TFTP_DOWNLOAD - bne :+ - jsr set_tftp_params - jsr tftp_download - jmp @after_tftp_call -: - cpy #NB65_DNS_RESOLVE bne :+ ldy #NB65_DNS_HOSTNAME+1 @@ -171,7 +140,7 @@ irq_handler_installed: dey lda (nb65_params),y jsr dns_set_hostname - bcs @dns_error + bcc @dns_error jsr dns_resolve bcs @dns_error ldy #NB65_DNS_HOSTNAME_IP @@ -182,7 +151,9 @@ irq_handler_installed: iny dex bne @copy_dns_ip +@dns_error: rts + : cpy #NB65_UDP_ADD_LISTENER @@ -299,11 +270,52 @@ irq_handler_installed: rts : + + cpy #NB65_TFTP_DIRECTORY_LISTING + bne :+ + phax + jsr set_tftp_params + jsr tftp_directory_listing + +@after_tftp_call: ;write the current load address back to the param buffer (so if $0000 was passed in, the caller can find out the actual value used) + plax + bcs @tftp_error + stax nb65_params + + ldy #NB65_TFTP_POINTER + lda tftp_load_address + sta (nb65_params),y + iny + lda tftp_load_address+1 + sta (nb65_params),y + + ldy #NB65_TFTP_FILESIZE + lda tftp_filesize + sta (nb65_params),y + iny + lda tftp_filesize+1 + sta (nb65_params),y + clc +@tftp_error: + rts +: + + cpy #NB65_TFTP_DOWNLOAD + bne :+ + phax + jsr set_tftp_params + jsr tftp_download + jmp @after_tftp_call +: + + cpy #NB65_TFTP_CALLBACK_DOWNLOAD bne :+ + phax jsr set_tftp_params jsr set_tftp_callback_vector - jmp tftp_download + jsr tftp_download + jmp @after_tftp_call : cpy #NB65_TFTP_CALLBACK_UPLOAD diff --git a/client/ip65/tftp.s b/client/ip65/tftp.s index 7ae357e..b8af3b3 100644 --- a/client/ip65/tftp.s +++ b/client/ip65/tftp.s @@ -21,6 +21,7 @@ .export tftp_set_callback_vector .export tftp_data_block_length .export tftp_clear_callbacks + .export tftp_filesize .import ip65_process .import ip65_error @@ -91,15 +92,16 @@ tftp_actual_server_ip: .res 4 ;this is read from the reply - it may not be t 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 .code -;uploads a file to a tftp server +;uploads a file to a tftp server with data retrieved from specified memory location ; 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 +; 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 @@ -109,6 +111,18 @@ tftp_opcode: .res 2 ; will be set to 4 if we are doing a RRQ, or 7 if we are doi ; 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_from_memory: + ldax #copy_ram_to_tftp_block + jsr tftp_set_callback_vector + +;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 @@ -153,7 +167,10 @@ tftp_directory_listing: ; 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: - ldax #$0100 ;opcode 01 = RRQ + 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 @@ -503,6 +520,10 @@ copy_tftp_block_to_ram: sta tftp_current_memloc+1 rts +;default handler for uploading a file +copy_ram_to_tftp_block: + rts + ;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. diff --git a/client/test/test_cart_api.s b/client/test/test_cart_api.s index 29c42e1..cf8f1cb 100644 --- a/client/test/test_cart_api.s +++ b/client/test/test_cart_api.s @@ -174,8 +174,15 @@ init: stax nb65_param_buffer+NB65_TFTP_POINTER ldax #nb65_param_buffer call #NB65_TFTP_CALLBACK_DOWNLOAD - - + lda #'$' + jsr print_a + lda nb65_param_buffer+NB65_TFTP_FILESIZE+1 + jsr print_hex + lda nb65_param_buffer+NB65_TFTP_FILESIZE + jsr print_hex + print #bytes_download + print_cr + ;udp callback test ldax #64 ;listen on port 64 @@ -409,6 +416,8 @@ ok: dns_lookup_failed_msg: .byte "DNS LOOKUP FAILED", 0 +bytes_download: .byte "BYTES DOWNLOADED",13,0 + reply_message: .byte "PONG!" reply_message_end: diff --git a/doc/nb65_api_technical_reference.doc b/doc/nb65_api_technical_reference.doc index d8a92668b872b9dbba345e97a3e83ef87d88938d..69e4182ab12a9389c19c8b5d5bf969c8e9b8aa6e 100644 GIT binary patch delta 8652 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 delta 7298 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