; ############# ; KIPPER KART - A C64 TCP/IP stack as a 16KB cartridge ; jonno@jamtronix.com .macro print_failed ldax #failed_msg jsr print_ascii_as_native jsr print_cr .endmacro .macro print_ok ldax #ok_msg jsr print_ascii_as_native jsr print_cr .endmacro .macro kippercall arg ldy arg jsr KPR_DISPATCH_VECTOR .endmacro .ifndef KPR_API_VERSION_NUMBER .define EQU = .include "../inc/kipper_constants.i" .endif .include "../inc/common.i" .include "../inc/c64keycodes.i" .include "../inc/menu.i" .include "../inc/ping.i" .include "../inc/sidplay.i" .include "../inc/disk_transfer.i" .import cls .import beep .import exit_to_basic .import timer_vbl_handler .import kipper_dispatcher .import ip65_process .import ip65_init .import get_filtered_input .import filter_text .import filter_dns .import filter_ip .import arp_calculate_gateway_mask .import parse_dotted_quad .import dotted_quad_value .import parse_integer .import print_integer .import get_key_ip65 .import cfg_ip .import cfg_netmask .import cfg_gateway .import cfg_dns .import cfg_tftp_server .import print_ascii_as_native .import print_dotted_quad .import print_hex .import print_errorcode .import print_ip_config .import ok_msg .import failed_msg .import init_msg .import ip_address_msg .import netmask_msg .import gateway_msg .import dns_server_msg .import tftp_server_msg .import press_a_key_to_continue .import cfg_default_drive .import print_a .import print_cr .import print .import copymem .importzp copy_src .importzp copy_dest .import get_filtered_input .import __DATA_LOAD__ .import __DATA_RUN__ .import __DATA_SIZE__ .import __SELF_MODIFIED_CODE_LOAD__ .import __SELF_MODIFIED_CODE_RUN__ .import __SELF_MODIFIED_CODE_SIZE__ .import cfg_tftp_server kipper_param_buffer = $6000 directory_buffer = $4020 .bss temp_ptr: .res 2 .segment "SELF_MODIFIED_CODE" call_downloaded_prg: jsr $0000 ;overwritten when we load a file jmp cold_init get_value_of_axy: ;some more self-modifying code lda $ffff,y rts .segment "CARTRIDGE_HEADER" .word cold_init ;cold start vector .word warm_init ;warm start vector .byte $C3,$C2,$CD,$38,$30 ; "CBM80" .byte "KIPPER" ; API signature jmp kipper_dispatcher ; KPR_DISPATCH_VECTOR : entry point for KIPPER functions jmp ip65_process ;KPR_PERIODIC_PROCESSING_VECTOR : routine to be periodically called to check for arrival of ethernet packets .byte $0,$0,$0 ;reserved for future use .code cold_init: jsr init_tod ;first let the kernal do a normal startup sei jsr $fda3 ;initialize CIA I/O jsr $fd50 ;RAM test, set pointers jsr $fd15 ;set vectors for KERNAL jsr $ff5B ;init. VIC cli ;KERNAL init. finished warm_init: lda #14 jsr print_a ;switch to lower case ;set some funky colours LDA #$04 ;purple STA $D020 ;border LDA #$00 ;black STA $D021 ;background lda #$05 ;petscii for white text jsr print_a ;relocate our r/w data ldax #__DATA_LOAD__ stax copy_src ldax #__DATA_RUN__ stax copy_dest ldax #__DATA_SIZE__ jsr copymem ;relocate the self-modifying code (if necessary) ldax #__SELF_MODIFIED_CODE_LOAD__ stax copy_src ldax #__SELF_MODIFIED_CODE_RUN__ stax copy_dest ldax #__SELF_MODIFIED_CODE_SIZE__ jsr copymem ldax #netboot65_msg jsr print_ascii_as_native ldax #init_msg+1 jsr print_ascii_as_native kippercall #KPR_INITIALIZE bcc init_ok print_failed jsr print_errorcode jsr wait_for_keypress jmp exit_to_basic print_main_menu: jsr cls ldax #netboot65_msg jsr print_ascii_as_native ldax #main_menu_msg jmp print_ascii_as_native init_ok: ;look for an 'autoexec' file jsr print_cr ldax #loading_msg jsr print_ascii_as_native ldax #autoexec_filename stax io_filename jsr print_ascii_as_native jsr print_cr ldax #$0000 jsr io_read_file bcs main_menu @file_read_ok: ldax #load_ok_msg jsr print_ascii_as_native ldax io_load_address jmp boot_into_file main_menu: jsr print_main_menu jsr print_ip_config jsr print_default_drive jsr print_cr @get_key: jsr get_key_ip65 cmp #KEYCODE_F1 bne @not_f1 jmp @tftp_boot @not_f1: cmp #KEYCODE_F2 bne @not_f2 jmp disk_boot @not_f2: cmp #KEYCODE_F3 bne @not_f3 jsr upload_d64 jmp main_menu @not_f3: cmp #KEYCODE_F4 bne @not_f4 jsr d64_download jmp main_menu @not_f4: cmp #KEYCODE_F5 bne @not_f5 jmp netplay_sid @not_f5: cmp #KEYCODE_F6 bne @not_f6 jsr cls ldax #ping_header jsr print_ascii_as_native jsr ping_loop jmp exit_ping @not_f6: cmp #KEYCODE_F7 beq @change_config jmp @get_key @exit_to_prog: ldax #$fe66 ;do a wam start jmp call_downloaded_prg @change_config: jsr cls @config_menu: ldax #netboot65_msg jsr print_ascii_as_native ldax #config_menu_msg jsr print_ascii_as_native jsr print_ip_config jsr print_default_drive jsr print_cr @get_key_config_menu: jsr get_key_ip65 cmp #KEYCODE_ABORT bne @not_abort jmp main_menu @not_abort: cmp #KEYCODE_F1 bne @not_ip ldax #new jsr print_ascii_as_native ldax #ip_address_msg jsr print_ascii_as_native jsr print_cr ldax #filter_ip ldy #20 jsr get_filtered_input bcs @no_ip_address_entered jsr parse_dotted_quad bcc @no_ip_resolve_error jmp @change_config @no_ip_resolve_error: ldax #dotted_quad_value stax copy_src ldax #cfg_ip stax copy_dest ldax #4 jsr copymem @no_ip_address_entered: jmp @change_config @not_ip: cmp #KEYCODE_F2 bne @not_netmask ldax #new jsr print_ascii_as_native ldax #netmask_msg jsr print_ascii_as_native jsr print_cr ldax #filter_ip ldy #20 jsr get_filtered_input bcs @no_netmask_entered jsr parse_dotted_quad bcc @no_netmask_resolve_error jmp @change_config @no_netmask_resolve_error: ldax #dotted_quad_value stax copy_src ldax #cfg_netmask stax copy_dest ldax #4 jsr copymem @no_netmask_entered: jmp @change_config @not_netmask: cmp #KEYCODE_F3 bne @not_gateway ldax #new jsr print_ascii_as_native ldax #gateway_msg jsr print_ascii_as_native jsr print_cr ldax #filter_ip ldy #20 jsr get_filtered_input bcs @no_gateway_entered jsr parse_dotted_quad bcc @no_gateway_resolve_error jmp @change_config @no_gateway_resolve_error: ldax #dotted_quad_value stax copy_src ldax #cfg_gateway stax copy_dest ldax #4 jsr copymem jsr arp_calculate_gateway_mask ;we have modified our netmask, so we need to recalculate gw_test @no_gateway_entered: jmp @change_config @not_gateway: cmp #KEYCODE_F4 bne @not_dns_server ldax #new jsr print_ascii_as_native ldax #dns_server_msg jsr print_ascii_as_native jsr print_cr ldax #filter_ip ldy #20 jsr get_filtered_input bcs @no_dns_server_entered jsr parse_dotted_quad bcc @no_dns_resolve_error jmp @change_config @no_dns_resolve_error: ldax #dotted_quad_value stax copy_src ldax #cfg_dns stax copy_dest ldax #4 jsr copymem @no_dns_server_entered: jmp @change_config @not_dns_server: cmp #KEYCODE_F5 bne @not_tftp_server ldax #new jsr print_ascii_as_native ldax #tftp_server_msg jsr print_ascii_as_native jsr print_cr ldax #filter_dns ldy #40 jsr get_filtered_input bcs @no_server_entered stax kipper_param_buffer jsr print_cr ldax #resolving jsr print_ascii_as_native ldax #kipper_param_buffer kippercall #KPR_DNS_RESOLVE bcs @resolve_error ldax #kipper_param_buffer stax copy_src ldax #cfg_tftp_server stax copy_dest ldax #4 jsr copymem @no_server_entered: jmp @change_config @not_tftp_server: cmp #KEYCODE_F6 bne @not_reset jsr ip65_init ;this will reset everything jmp @change_config @not_reset: cmp #KEYCODE_F7 bne @not_main_menu jmp main_menu @not_main_menu: cmp #'+' bne @not_plus inc cfg_default_drive jmp @config_menu @not_plus: cmp #'-' bne @not_minus dec cfg_default_drive jmp @config_menu @not_minus: jmp @get_key_config_menu @resolve_error: print_failed jsr wait_for_keypress jsr @change_config @tftp_boot: ldax #tftp_dir_filemask jsr get_tftp_directory_listing bcs return_to_main @boot_filename_set: ;AX now points to filename jsr download bcc file_downloaded_ok tftp_boot_failed: jsr wait_for_keypress return_to_main: jmp error_handler file_downloaded_ok: ldax kipper_param_buffer+KPR_TFTP_POINTER boot_into_file: stax kipper_param_buffer ;use the param buffer as a temp holding place for the load address ;get ready to bank out kippercall #KPR_DEACTIVATE jsr $ffe7 ; make sure all files have been closed. ;check whether the file we just downloaded was a BASIC prg lda kipper_param_buffer cmp #01 bne @not_a_basic_file lda kipper_param_buffer+1 cmp #$08 bne @not_a_basic_file lda $805 cmp #$9e ;opcode for 'SYS' bne @not_a_basic_stub ldax #$806 ;should point to ascii string containing address that was to be SYSed jsr parse_integer jmp exit_cart_via_ax ;good luck! @not_a_basic_stub: ldax #cant_boot_basic jsr print_ascii_as_native jsr wait_for_keypress jmp warm_init @not_a_basic_file: ldax kipper_param_buffer exit_cart_via_ax: sta call_downloaded_prg+1 stx call_downloaded_prg+2 jmp call_downloaded_prg get_tftp_directory_listing: stax temp_ptr @get_listing: stax kipper_param_buffer+KPR_TFTP_FILENAME ldax #directory_buffer stax kipper_param_buffer+KPR_TFTP_POINTER ldax #getting_dir_listing_msg jsr print_ascii_as_native ldax #kipper_param_buffer kippercall #KPR_TFTP_DOWNLOAD bcs @dir_failed lda directory_buffer ;get the first byte that was downloaded bne :+ jmp @no_files_on_server : ldax #directory_buffer ldy #1 ;filenames will be ASCII jsr select_option_from_menu bcc @tftp_filename_set rts @tftp_filename_set: stax copy_dest stax get_value_of_axy+1 ldy #0 jsr get_value_of_axy ;A now == first char in string we just downloaded cmp #'$' bne @not_directory_name ;it's a directory name, so we need to append the file mask to end of it ;this will fail if the file path is more than 255 characters long @look_for_trailing_zero: iny inc copy_dest bne :+ inc copy_dest+1 : jsr get_value_of_axy ;A now == next char in string we just downloaded bne @look_for_trailing_zero ; got trailing zero ldax temp_ptr clc adc #1 ;skip the leading '$' bcc :+ inx : stax copy_src ldax #$07 jsr copymem ldax get_value_of_axy+1 jmp @get_listing @not_directory_name: ldax get_value_of_axy+1 clc rts @dir_failed: ldax #dir_listing_fail_msg jsr print_ascii_as_native sec rts @no_files_on_server: ldax #no_files jsr print_ascii_as_native sec rts disk_boot: .import io_read_catalogue .import io_device_no .import io_filename .import io_read_file .import io_load_address lda cfg_default_drive sec sbc #7 sta io_device_no ldax #directory_buffer jsr io_read_catalogue lda directory_buffer ;get the first byte that was downloaded bne :+ jmp @no_files_on_disk : ldax #directory_buffer ldy #0 ;filenames will NOT be ASCII jsr select_option_from_menu bcc @disk_filename_set jmp main_menu @dir_failed: ldax #dir_listing_fail_msg @print_error: jsr print_ascii_as_native jsr print_errorcode jsr print_cr jmp @wait_keypress_then_return_to_main @no_files_on_disk: ldax #no_files jsr print_ascii_as_native @wait_keypress_then_return_to_main: jsr wait_for_keypress jmp main_menu @disk_filename_set: stax io_filename ldax #loading_msg jsr print_ascii_as_native ldax io_filename jsr print_ascii_as_native jsr print_cr ldax #$0000 jsr io_read_file bcc @file_read_ok ldax #file_read_error jmp @print_error @file_read_ok: ldax #load_ok_msg jsr print_ascii_as_native ldax io_load_address jmp boot_into_file error_handler: jsr print_errorcode jsr print_cr jsr wait_for_keypress jmp main_menu netplay_sid: ldax #sid_filemask jsr get_tftp_directory_listing bcc @sid_filename_set jmp error_handler @sid_filename_set: ;AX now points to filename stax kipper_param_buffer+KPR_TFTP_FILENAME ldax #$1000 ;load address stax kipper_param_buffer+KPR_TFTP_POINTER jsr download2 bcc :+ jmp error_handler : jsr cls ldax kipper_param_buffer+KPR_TFTP_FILESIZE stax sidfile_length ldax kipper_param_buffer+KPR_TFTP_POINTER jsr load_sid jsr play_sid jmp main_menu d64_download: ldax #d64_filemask jsr get_tftp_directory_listing bcc @d64_filename_set jmp main_menu @d64_filename_set: ;AX now points to filename jsr download_d64 jmp main_menu bad_boot: jsr wait_for_keypress jmp $fe66 ;do a wam start download: ;AX should point at filename to download stax kipper_param_buffer+KPR_TFTP_FILENAME ldax #$0000 ;load address will be first 2 bytes of file we download (LO/HI order) stax kipper_param_buffer+KPR_TFTP_POINTER download2: ldax #downloading_msg jsr print_ascii_as_native ldax kipper_param_buffer+KPR_TFTP_FILENAME jsr print_ascii_as_native jsr print_cr ldax #kipper_param_buffer kippercall #KPR_TFTP_DOWNLOAD bcc :+ ldax #tftp_download_fail_msg jsr print_ascii_as_native jsr print_errorcode sec rts : ldax #tftp_download_ok_msg jsr print_ascii_as_native clc rts wait_for_keypress: ldax #press_a_key_to_continue jsr print_ascii_as_native @loop: jsr $ffe4 beq @loop rts get_key: @loop: jsr KPR_PERIODIC_PROCESSING_VECTOR jsr $ffe4 beq @loop rts cfg_get_configuration_ptr: ldax #kipper_param_buffer kippercall #KPR_GET_IP_CONFIG rts exit_ping: lda #$05 ;petscii for white text jsr print_a jmp main_menu print_default_drive: ldax #default_drive jsr print_ascii_as_native lda cfg_default_drive jsr print_hex jmp print_cr ;init the Time-Of-Day clock - cribbed from http://codebase64.org/doku.php?id=base:initialize_tod_clock_on_all_platforms init_tod: sei lda #0 sta $d011 ;Turn off display to disable badlines sta $dc0e ;Set TOD Clock Frequency to 60Hz sta $dc0f ;Enable Set-TOD-Clock sta $dc0b ;Set TOD-Clock to 0 (hours) sta $dc0a ;- (minutes) sta $dc09 ;- (seconds) sta $dc08 ;- (deciseconds) lda $dc08 ; @wait_raster: cmp $dc08 ;Sync raster to TOD Clock Frequency beq @wait_raster ldx #0 ;Prep X and Y for 16 bit ldy #0 ; counter operation lda $dc08 ;Read deciseconds @loop1: inx ;2 -+ bne @loop2 ;2/3 | Do 16 bit count up on iny ;2 | X(lo) and Y(hi) regs in a jmp @loop3 ;3 | fixed cycle manner @loop2: nop ;2 | nop ;2 -+ @loop3: cmp $dc08 ;4 - Did 1 decisecond pass? beq @loop1 ;3 - If not, loop-di-doop ;Each loop = 16 cycles ;If less than 118230 cycles passed, TOD is ;clocked at 60Hz. If 118230 or more cycles ;passed, TOD is clocked at 50Hz. ;It might be a good idea to account for a bit ;of slack and since every loop is 16 cycles, ;28*256 loops = 114688 cycles, which seems to be ;acceptable. That means we need to check for ;a Y value of 28. cpy #28 ;Did 114688 cycles or less go by? bcc @hertz_correct ;- Then we already have correct 60Hz $dc0e value lda #$80 ;Otherwise, we need to set it to 50Hz sta $dc0e @hertz_correct: lda #$1b ;Enable the display again sta $d011 cli rts .rodata netboot65_msg: .byte $13,10,"KipperKart V" .include "../inc/version.i" .byte 10,0 main_menu_msg: .byte 10,"Main Menu",10,10 .byte "F1: TFTP Boot F2: Disk Boot",10 .byte "F3: Upload D64 F4: Download D64",10 .byte "F5: SID Netplay F6: Ping",10 .byte "F7: Config",10,10 .byte 0 config_menu_msg: .byte 10,"Configuration",10,10 .byte "F1: IP Address F2: Netmask",10 .byte "F3: Gateway F4: DNS Server",10 .byte "F5: TFTP Server F6: Reset To Default",10 .byte "F7: Main Menu +/- Drive #",10,10 .byte 0 cant_boot_basic: .byte "BASIC file execution not supported",10,0 ping_header: .byte "ping",10,0 file_read_error: .asciiz "Error reading file" autoexec_filename: .byte "AUTOEXEC.PRG",0 downloading_msg: .byte "down" loading_msg: .asciiz "loading " uploading_msg: .byte "uploading ",0 getting_dir_listing_msg: .byte "fetching directory",10,0 dir_listing_fail_msg: .byte "directory listing failed",10,0 tftp_download_fail_msg: .byte "download failed", 10, 0 default_drive: .byte "Use Drive # : $",0 tftp_download_ok_msg: .byte "down" load_ok_msg: .byte "load OK", 10, 0 current: .byte "current ",0 new: .byte"new ",0 tftp_dir_filemask: .asciiz "$/*.prg" d64_filemask: .asciiz "$/*.d64" sid_filemask: .asciiz "$/*.sid" no_files: .byte "no files",10,0 resolving: .byte "resolving ",0 remote_host: .byte "hostname (return to quit)",10,": ",0 ;-- LICENSE FOR kipperkart.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 netboot65. ; ; 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 --