;;; ;;; MCFS2 kernel version 2.0.1 ;;; By: Daniel Tufvesson 2017 ;;; monitor_return: equ $c000 ; return to monitor console_status: equ $7fe5 ; console status vector console_output: equ $7fe8 ; console output vector console_input: equ $7feB ; console input vector page_register: equ $0002 ; I/O page select register ;;; ;;; RTC-72421 registers ;;; rtc_base: equ $9fc0 rtc_reg_s1: equ rtc_base+$0 ; 1-second digit rtc_reg_s10: equ rtc_base+$1 ; 10-seconds digit rtc_reg_mi1: equ rtc_base+$2 ; 1-minute digit rtc_reg_mi10: equ rtc_base+$3 ; 10-minutes digit rtc_reg_h1: equ rtc_base+$4 ; 1-hour digit rtc_reg_h10: equ rtc_base+$5 ; 10-hours digit rtc_reg_d1: equ rtc_base+$6 ; 1-day digit rtc_reg_d10: equ rtc_base+$7 ; 10-days digit rtc_reg_mo1: equ rtc_base+$8 ; 1-month digit rtc_reg_mo10: equ rtc_base+$9 ; 10-months digit rtc_reg_y1: equ rtc_base+$a ; 1-year digit rtc_reg_y10: equ rtc_base+$b ; 10-years digit rtc_reg_w: equ rtc_base+$c ; Day of week rtc_reg_cd: equ rtc_base+$d ; Control reg D rtc_reg_ce: equ rtc_base+$e ; Control reg E rtc_reg_cf: equ rtc_base+$f ; Control reg F ;;; ;;; CF registers ;;; cf_base: equ $9fd0 cf_reg_0: equ cf_base+0 ; data port cf_reg_1: equ cf_base+1 ; read: error code, write: feature cf_reg_2: equ cf_base+2 ; number of sectors to transfer cf_reg_3: equ cf_base+3 ; sector address LBA 0 [0:7] cf_reg_4: equ cf_base+4 ; sector address LBA 1 [8:15] cf_reg_5: equ cf_base+5 ; sector address LBA 2 [16:23] cf_reg_6: equ cf_base+6 ; sector address LBA 3 [24:27 (LSB of reg)] cf_reg_7: equ cf_base+7 ; read: status, write: command ;;; ;;; CF commands ;;; cf_cmd_reset: equ $04 *cf_cmd_id: equ $ec cf_cmd_read: equ $20 cf_cmd_write: equ $30 ;;; ;;; Program begin here ;;; org $d000 jmp cold_start jmp warm_start jmp ext_call ;;; ;;; Texts ;;; txt_init: fcc "MCFS 2.0.1" fcb $0d,$0a,$00 txt_prompt: fcc " # " fcb $00 txt_term_bs: fcb $08,$20,$08,$00 txt_error: fcc "Error: " fcb $00 txt_err_not_found: fcc "Not found" fcb $00 txt_err_exist: fcc "Already exist" fcb $00 txt_err_no_space: fcc "No space left" fcb $00 txt_err_not_empty: fcc "Not empty" fcb $00 txt_err_argument: fcc "Missing or malformed argument" fcb $00 txt_err_denied: fcc "Access denied" fcb $00 txt_err_not_exec: fcc "Not executable" fcb $00 txt_saving: fcc "Saving " fcb $00 txt_loading: fcc "Loading " fcb $00 txt_time_not_set: fcc "System time is not set" fcb $0d,$0a,$00 txt_help: fcc "Commands:" fcb $0d,$0a,$00 txt_help_settime: fcc "Format YYYY MM DD HH MM SS" fcb $0d,$0a,$00 ;;; ;;; Name of system dir ;;; txt_sys_dir: fcc "system" fcb $00 ;;; ;;; Program begin here ;;; cold_start: jsr print_crlf ldaa #$f0 staa page_register ;;; Clear variable area ldx #beg_of_vars var_clear_loop: clr ,x inx cpx #end_of_vars bls var_clear_loop ;;; Init CF card ldx #txt_init jsr print_string jsr cf_init jsr cf_check_error beq *+5 jmp start_error ;;; Goto root directry jsr open_volume bne start_error jmp prompt start_error: * ldaa #'! * jsr console_output staa temp ldx #txt_error jsr print_string ldx #temp jsr print_2hex jsr print_crlf jmp monitor_return ;;; ;;; Variable checksum routine ;;; var_checksum_calc: ldaa #$47 ; random non-zero value adda media_addr_3 adda media_addr_2 adda volume_beg adda volume_beg+1 adda volume_end adda volume_end+1 adda alloc_beg adda alloc_beg+1 adda alloc_end adda alloc_end+1 adda root_dir_beg adda root_dir_beg+1 adda root_dir_end adda root_dir_end+1 adda wrk_dir_beg adda wrk_dir_beg+1 adda wrk_dir_end adda wrk_dir_end+1 rts ;;; ;;; Print error code in A and return to prompt ;;; error_general: staa temp ldx #txt_error jsr print_string ldx #temp jsr print_2hex jsr print_crlf jmp prompt ;;; ;;; Print pretty error messages and return to prompt ;;; error_not_found: tab ldx #txt_err_not_found bra error_print error_exist: tab ldx #txt_err_exist bra error_print error_argument: tab ldx #txt_err_argument bra error_print error_no_space: tab ldx #txt_err_no_space bra error_print error_not_empty: tab ldx #txt_err_not_empty bra error_print error_denied: tab ldx #txt_err_denied bra error_print error_not_exec: tab ldx #txt_err_not_exec bra error_print error_print: pshx ldx #txt_error jsr print_string pulx jsr print_string jsr print_space ldaa #'( jsr console_output stab temp ldx #temp jsr print_2hex ldaa #') jsr console_output jsr print_crlf jmp prompt ;;; ;;; Return from user program ;;; warm_start: ldaa #$f0 staa page_register jsr var_checksum_calc cmpa var_checksum beq *+5 jmp cold_start jsr cf_init jsr cf_check_error beq *+5 jmp cold_start ;;; ;;; Read prompt ;;; prompt: jsr update_ram_time ldx #wrk_path jsr print_string ldx #txt_prompt jsr print_string ldx #line_buffer clr ,x prompt_loop: jsr console_input cmpa #$08 ; is it back space? beq prompt_bs cmpa #$0d ; is it carriage return? beq prompt_end cmpa #$1f ; ignore control characters bls prompt_loop cmpa #$7f ; ignore control characters bhi prompt_loop cpx #line_end beq prompt_loop jsr console_output staa ,x inx bra prompt_loop prompt_bs: cpx #line_buffer beq prompt_loop dex clr ,x pshx ldx #txt_term_bs jsr print_string pulx bra prompt_loop prompt_end: ;;; User has completed input jsr print_crlf clr ,x cpx #line_buffer beq prompt ldx wrk_dir_beg ; set work dir as current dir for commands stx cur_dir_beg ldx wrk_dir_end stx cur_dir_end jmp command_lookup ;;; ;;; Command table ;;; command_table: fdb cmd0 fdb cmd1 fdb cmd2 fdb cmd3 fdb cmd4 fdb cmd5 fdb cmd6 fdb cmd7 fdb cmd8 fdb cmd9 fdb cmd10 fdb cmd11 fdb cmd12 fdb cmd13 fdb cmd14 fcb 0 cmd0: fcc "ls" fcb 0 fdb command_dir cmd1: fcc "dir" fcb 0 fdb command_dir cmd2: fcc "cd" fcb 0 fdb command_cd cmd3: fcc "mkdir" fcb 0 fdb command_mkdir cmd4: fcc "save" fcb 0 fdb command_save cmd5: fcc "load" fcb 0 fdb command_load cmd6: fcc "run" fcb 0 fdb command_run cmd7: fcc "delete" fcb 0 fdb command_delete cmd8: fcc "rename" fcb 0 fdb command_rename cmd9: fcc "attr" fcb 0 fdb command_attr cmd10: fcc "touch" fcb 0 fdb command_touch cmd11: fcc "time" fcb 0 fdb command_time cmd12: fcc "settime" fcb 0 fdb command_settime cmd13: fcc "monitor" fcb 0 fdb command_monitor cmd14: fcc "help" fcb 0 fdb command_help ;;; ;;; Command lookup ;;; command_lookup: ldx #command_table stx xtemp command_loop: ldx #line_buffer stx line_pointer ldx xtemp tst ,x bne *+5 jmp command_lookup_dir ; no built in command found - check system dir ldx ,x jsr string_compare beq command_found ldx xtemp inx inx stx xtemp bra command_loop command_found: pshx ldx #line_buffer stx line_pointer pulx inx ldx ,x jmp ,x jmp prompt ;;; ;;; Command look for executable file ;;; command_lookup_dir: ;;; Check for file in 'system' dir ldx root_dir_beg stx cur_dir_beg ldx root_dir_end stx cur_dir_end ldx #txt_sys_dir stx line_pointer jsr find_file_entry beq *+4 bra command_lookup_dir_not ;;; System directory found ldaa ,x anda #$C1 cmpa #$C1 ; check entry is a directory and readable beq *+4 bra command_lookup_dir_not ;;; Enter directory ldd 1,x std cur_dir_beg ldd 3,x std cur_dir_end ;;; Check for command file in system dir ldx #line_buffer stx line_pointer jsr find_file_entry ; look for file beq *+4 bra command_lookup_dir_not ldd #line_buffer std line_pointer jmp execute_file command_lookup_dir_not: jmp error_not_found ;;; ;;; External call - main entry ;;; IN: pointer to file control block in X ;;; OUT: updated file control block ;;; ext_call: ldaa #$f0 staa page_register ldaa #$ff ; prepare general error code staa 1,x ; save error code in FCB jsr cf_init jsr cf_check_error beq *+3 rts jsr var_checksum_calc ; check checksum cmpa var_checksum beq *+3 rts ext_call_do: stx ext_fcb ; save pointer to control block ldd wrk_dir_beg ; set work dir as current dir for commands std cur_dir_beg ldd wrk_dir_end std cur_dir_end ldaa ,x ; get command cmpa #$01 ; command $01 - check file bne *+5 jmp ext_check_file cmpa #$07 ; command $07 - rename file bne *+5 jmp ext_rename cmpa #$08 ; command $08 - delete file bne *+5 jmp ext_delete cmpa #$10 ; command $10 - load file bne *+5 jmp ext_load_file cmpa #$11 ; command $11 - load file sector bne *+5 jmp ext_load_file_sec cmpa #$20 ; command $20 - save file bne *+5 jmp ext_save_file ldaa #$fe ; error code - illegal command staa 1,x ; save error code in FCB rts ;;; ;;; External call - check file ;;; IN: ptr to FCB in X and ext_fcb ;;; ext_check_file: ldd 2,x ; set file name std line_pointer ;;; Handle special case of checking current directory "." ldx 2,x ; load file name pointer ldx ,x ; load first two characters of name cpx #$2E00 ; is it "."? bne ext_check_file_find ldx ext_fcb ldaa #$C1 staa 4,x ldd wrk_dir_end subd wrk_dir_beg addd #1 std 5,x ; store sector count clr 1,x ; error code ok rts ;;; Look for file ext_check_file_find: jsr find_file_entry bne ext_check_file_end ;;; File entry found stx xtemp ; save file entry pointer ldaa ,x ; read flags ldx ext_fcb staa 4,x ldx xtemp ldd 3,x ; calc number of sectors in file subd 1,x addd #1 ldx ext_fcb std 5,x ; save number of sectors ;;; Copy remaining part on file entry ;;; file size and time stamp - bytes 6 to 14 ldab #5 ext_check_file_loop: ldx xtemp abx ldaa ,x ldx ext_fcb abx staa 2,x incb cmpb #15 bls ext_check_file_loop clra ; error code ok ext_check_file_end: ldx ext_fcb ; save error code to fcb staa 1,x rts ;;; ;;; External call - rename ;;; IN: ptr to FCB in X and ext_fcb ;;; ext_rename: ;;; Check if new file name valid ldx 4,x stx line_pointer jsr validate_filename_arg bcs *+5 jmp ext_ren_err ;;; Check if new entry name exists jsr find_file_entry bne *+5 jmp ext_ren_err ;;; Check if old entry name exists ldx ext_fcb ldx 2,x stx line_pointer jsr find_file_entry beq *+5 jmp ext_ren_err ;;; Check if file is writable ldaa ,x bita #$02 bne *+5 jmp ext_ren_err ;;; Update directory entry name pshx ldx ext_fcb ldx 4,x ; get new name pointer stx line_pointer pulx ldab #16 ; go to start of entry file name abx ldab #31 ext_rename_name: ; copy name char by char jsr line_pull_char tsta beq ext_rename_name_e cmpa #$20 beq ext_rename_name_e staa ,x inx decb bne ext_rename_name ext_rename_name_e: clra staa ,x ; terminate name sting ;;; Write back directory sector ldx #sector_buffer jsr write_sector beq *+5 jmp ext_ren_end ext_ren_done: clra ; error code OK ext_ren_end: ldx ext_fcb ; save error code to fcb staa 1,x rts ext_ren_err: ldaa #$F1 bra ext_ren_end ;;; ;;; External call - delete ;;; IN: ptr to FCB in X and ext_fcb ;;; ext_delete: ;;; Move line_pointer to file name (first argument) ldd 2,x ; set file name std line_pointer std arg_pointer ;;; Check if file exists jsr find_file_entry ; lookup directory entry beq *+5 jmp ext_del_end ldaa ,x ; get entry flags bita #$02 ; check if writable bne *+5 jmp ext_del_end bita #$40 beq ext_del_do ; entry is a file - perform delete jsr check_dir_empty ; entry is a directory - check if empty beq *+5 jmp ext_del_end ldx arg_pointer ; retore line_pointer (point to file name) stx line_pointer jsr find_file_entry ; lookup directory entry again beq *+5 jmp ext_del_end: ext_del_do: ldd 1,x ; file start sector std cur_file_beg ldd 3,x ; file end sector std cur_file_end ;;; Clear directory entry for file ldaa #64 ext_del_clear_entry: clr ,x inx deca bne ext_del_clear_entry ;;; write back directory sector ldx #sector_buffer jsr write_sector beq *+5 jmp ext_del_end ;;; Free allocated sectors jsr dealloc beq *+5 jmp ext_del_end ext_del_done: clra ; error code OK ext_del_end: ldx ext_fcb ; save error code to fcb staa 1,x rts ;;; ;;; External call - load file ;;; IN: ptr to FCB in X and ext_fcb ;;; ext_load_file: ldd 2,x ; set file name std line_pointer ldd 4,x ; read file start address std cur_file_saddr ;;; Handle special case of loading current directory "." ldx 2,x ; load file name pointer ldx ,x ; load first two characters of name cpx #$2E00 ; is it "."? bne ext_load_file_find ;;; Load current directory (special case) ldd wrk_dir_beg std cur_file_beg ldd wrk_dir_end std cur_file_end ldd cur_file_saddr addd #$1000 std cur_file_eaddr bra ext_load_read_file_data ;;; Look for file ext_load_file_find: jsr find_file_entry bne ext_load_file_end ;;; File entry found stx xtemp ; save file entry pointer ldaa ,x ; read flags bita #$01 ; verify that file is readable bne ext_load_file_do ldaa #$F4 ; error code - access denied bra ext_load_file_end ;;; Load file in to memory ext_load_file_do: ldd 1,x ; get file start sector std cur_file_beg ldd 3,x ; get file end sector std cur_file_end ;;; Calculate file memory range ldd 7,x subd #1 addd cur_file_saddr std cur_file_eaddr ;;; Read file data sectors ext_load_read_file_data: ldx cur_file_beg ; set first sector to read stx media_addr_1 clrb ; no byte skip when reading buffer ext_load_file_loop: ldx #sector_buffer jsr read_sector beq *+4 bra ext_load_file_end jsr sector_buffer_out ; copy data from buffer ldx media_addr_1 cpx cur_file_end ; all data sectors written? beq ext_load_file_done inx stx media_addr_1 bra ext_load_file_loop ext_load_file_done: clra ext_load_file_end: ldx ext_fcb ; save error code to fcb staa 1,x rts ;;; ;;; External call - load file sector ;;; IN: ptr to FCB in X and ext_fcb ;;; ext_load_file_sec: ldd 2,x ; set file name std line_pointer ldd 4,x ; read file start address std cur_file_saddr ldd 6,x ; read file sector number (0-n) std cur_file_beg ;;; Handle special case of loading current directory "." ldx 2,x ; load file name pointer ldx ,x ; load first two characters of name cpx #$2E00 ; is it "."? bne ext_load_file_sec_find ;;; Load current directory (special case) ldd cur_file_beg addd wrk_dir_beg std cur_file_beg ldx cur_file_beg ; check requested sector is within file range cpx wrk_dir_end bls ext_load_file_sec_do ldaa #$F5 ; error code - value out of range bra ext_load_file_sec_end ;;; Look for file ext_load_file_sec_find: jsr find_file_entry bne ext_load_file_sec_end ;;; File entry found stx xtemp ; save file entry pointer ldaa ,x ; read flags bita #$01 ; verify that file is readable bne ext_load_file_sec_pre ldaa #$F4 ; error code - access denied bra ext_load_file_sec_end ;;; Prepare to load file sector in to memory ext_load_file_sec_pre: ldd cur_file_beg ; this contais the requested file sector number (0-n) addd 1,x std cur_file_beg ; this contais the requested media sector number ldd 3,x ; read file end sector std cur_file_end ldx cur_file_beg ; check requested sector is within file range cpx cur_file_end bls ext_load_file_sec_do ldaa #$F5 ; error code - value out of range bra ext_load_file_sec_end ;;; Load requested sector into memory ext_load_file_sec_do: ldd cur_file_beg ; set media address std media_addr_1 ldx cur_file_saddr ; set destination address jsr read_sector ; red sector into memory ext_load_file_sec_end: ldx ext_fcb ; save error code to fcb staa 1,x rts ;;; ;;; External call - save file ;;; IN: ptr to FCB in X and ext_fcb ;;; ext_save_file: ldd 2,x ; set file name std line_pointer std arg_pointer ldd 4,x ; read data start address std cur_file_saddr ldd 6,x ; read data end address std cur_file_eaddr ;;; Check if valid file name ldx line_pointer jsr validate_filename_arg bcs *+5 jmp ext_save_file_end ;;; Check if file already exists jsr find_file_entry bne ext_save_file_nexist ldaa #$F4 jmp ext_save_file_end ext_save_file_nexist: ;;; Determine number of sectors needed for the file ldaa #$01 staa temp ; sector counter (start at one sector) ldd cur_file_eaddr ; calculate file size subd cur_file_saddr ext_save_loop1: cmpa #$01 bls ext_save_loop1_done ; calculation done? subd #512 inc temp bra ext_save_loop1 ext_save_loop1_done: ;;; Allocate sectors for the file ldaa temp ; number of sectors to allocate jsr alloc beq *+5 ; enough free space left on volume for file? jmp ext_save_file_end ;;; Setup new directory entry jsr find_free_entry ; find the empty entry in current directory again beq *+5 jmp ext_save_file_end pshx ldx ext_fcb ldaa 8,x ; get file attributes from fcb anda #$07 oraa #$A0 ; set entry to status "in use file" pulx staa ,x ldd cur_file_beg ; set file start sector std 1,x ldd cur_file_end ; set file end sector std 3,x ldd cur_file_eaddr ; set file size subd cur_file_saddr addd #1 bcc ext_save_size_lsb inc 6,x ext_save_size_lsb: std 7,x ldd arg_pointer ; set file name std line_pointer ldab #9 ; go to start of time stamp abx jsr rtc_get_time ; write time stamp ldab #7 ; go to start of entry file name abx ldab #31 ext_save_name: ; copy name into file entry jsr line_pull_char tsta beq ext_save_name_done cmpa #$20 beq ext_save_name_done staa ,x inx decb bne ext_save_name ext_save_name_done: clra staa ,x ; terminate name sting ;;; Write back directory sector ldx #sector_buffer jsr write_sector beq *+5 jmp ext_save_file_end ;;; Write file data to data sectors ldx cur_file_beg ; set first sector to write stx media_addr_1 ext_save_data_loop: jsr sector_buffer_in ; copy data to buffer ldx #sector_buffer jsr write_sector beq *+5 jmp ext_save_file_end ldx media_addr_1 cpx cur_file_end ; all data sectors written? beq ext_save_complete inx stx media_addr_1 bra ext_save_data_loop ext_save_complete: clra ext_save_file_end: ldx ext_fcb ; save error code to fcb staa 1,x rts ;;; ;;; Pull one char from line buffer and update pointer ;;; line_pull_char: pshx ldx line_pointer ldaa ,x beq line_pull_char_e ; end of buffer inx stx line_pointer pulx rts line_pull_char_e: clra pulx rts ;;; ;;; Pull 2 hex chars from line buffer and store in temp ;;; OUT: Carry set on OK, value in temp ;;; line_pull_2hex: jsr line_pull_char jsr char_to_hex bcc line_pull_2hex_fail asla asla asla asla staa temp jsr line_pull_char jsr char_to_hex bcc line_pull_2hex_fail oraa temp staa temp clra sec rts line_pull_2hex_fail: clc rts ;;; ;;; Pull 4 hex chars from line buffer and store in xtemp ;;; OUT: Carry set on OK, value in xtemp ;;; line_pull_4hex: jsr line_pull_char jsr char_to_hex bcc line_pull_4hex_fail asla asla asla asla staa xtemp jsr line_pull_char jsr char_to_hex bcc line_pull_4hex_fail oraa xtemp staa xtemp jsr line_pull_char jsr char_to_hex bcc line_pull_4hex_fail asla asla asla asla staa xtemp+1 jsr line_pull_char jsr char_to_hex bcc line_pull_4hex_fail oraa xtemp+1 staa xtemp+1 clra sec rts line_pull_4hex_fail: clc rts ;;; ;;; Find next word in string pointed at by X ;;; str_skp_next: ldaa ,x beq str_skp_next_e cmpa #$20 beq str_skp_next_1 inx bra str_skp_next str_skp_next_1: ldaa ,x beq str_skp_next_e cmpa #$20 bne str_skp_next_e inx bra str_skp_next_1 str_skp_next_e: rts ;;; ;;; Convert ASCII char to hex ;;; IN: Chanracter in A ;;; OUT: Value in A. Carry set on OK ;;; char_to_hex: beq char_to_hex_fail ; end of string suba #$30 bmi char_to_hex_fail ; not hex cmpa #$09 ble char_to_hex_ok ; $9 and below anda #$df ; to upper case cmpa #$11 bmi char_to_hex_fail ; not hex cmpa #$16 bgt char_to_hex_fail ; not hex suba #7 char_to_hex_ok: sec rts char_to_hex_fail: clc rts ;;; ;;; String compare ;;; IN: line_pointer, X ;;; OUT: Z set on match ;;; string_compare: jsr line_pull_char ldab ,x cba bne string_cmp_not tsta beq string_cmp_not inx bra string_compare string_cmp_not: aba beq string_cmp_match cmpa #$20 beq string_cmp_match rts string_cmp_match: clra rts ;;; ;;; Print null terminated string ;;; print_string: ldaa ,x beq print_string_e jsr console_output inx bra print_string print_string_e: rts ;;; ;;; Print CR LF ;;; print_crlf: ldaa #$0d jsr console_output ldaa #$0a jmp console_output ;;; ;;; Print space ;;; print_space: ldaa #$20 jmp console_output ;;; ;;; Print HEX characters ;;; print_hex_l: lsra ; out hex left BCD lsra lsra lsra print_hex_r: anda #$f ; out hex right BCD adda #$30 cmpa #$39 bls *+4 adda #$7 jmp console_output print_2hex: ldaa ,x jsr print_hex_l ; out left hex char ldaa ,x inx jmp print_hex_r ; out right hex char print_4hex: bsr print_2hex ; print 4 hex chars bra print_2hex ; print 2 hex chars ;;; ;;; Validate file name argument pointed at by X ;;; OUT: Carry set on valid ;;; validate_filename_arg: tst ,x beq invalid_filename_arg ; no zero length name validate_filename_arg_l: ldaa ,x beq validate_filename_arg_e cmpa #$20 beq validate_filename_arg_e cmpa #$1F bls invalid_filename_arg cmpa #$7E bhi invalid_filename_arg cmpa #$22 ; '"' beq invalid_filename_arg cmpa #$27 ; ''' beq invalid_filename_arg cmpa #$2A ; '*' beq invalid_filename_arg cmpa #$2F ; '/' beq invalid_filename_arg cmpa #$5C ; '\' beq invalid_filename_arg cmpa #$60 ; '`' beq invalid_filename_arg inx bra validate_filename_arg_l validate_filename_arg_e: sec rts invalid_filename_arg: clc rts ;;; ;;; Print file attributes ;;; print_file_attr: ldaa #'- ldab ,x bitb #$40 beq *+4 ldaa #'d jsr console_output ldaa #'- ldab ,x bitb #$01 beq *+4 ldaa #'r jsr console_output ldaa #'- ldab ,x bitb #$02 beq *+4 ldaa #'w jsr console_output ldaa #'- ldab ,x bitb #$04 beq *+4 ldaa #'x jsr console_output rts ;;; ;;; Command 'dir' ;;; command_dir: ldx cur_dir_beg cmd_dir_loop1: cpx cur_dir_end ; compare with end of current directory bhi cmd_dir_done ; end if we have looped through entire directory stx media_addr_1 ;;; Read directory sector ldx #sector_buffer jsr read_sector ; read next sector of directory beq *+5 jmp error_general ;;; Print contents of directory sector ldx #sector_buffer cmd_dir_loop2: ldaa ,x beq cmd_dir_nxt_entry ; is the directory entry empty? stx xtemp ; save current position in sector_buffer bsr cmd_dir_print ; print directory entry + crlf ;;; Go to next directory entry ldx xtemp ; restore sector_buffer pointer cmd_dir_nxt_entry: ; goto next directory entry ldab #64 ; within current sector abx cpx #sector_buffer+512 bne cmd_dir_loop2 ;;; Go to next sector in directory ldx media_addr_1 inx bra cmd_dir_loop1 cmd_dir_done: jmp prompt ;;; ;;; Print directory entry pointed at by X ;;; cmd_dir_print: jsr print_file_attr inx * inx * inx * inx * inx jsr print_space jsr print_4hex ; start sector jsr print_space jsr print_4hex ; end sector jsr print_space jsr print_4hex ; file size jsr print_4hex ; file size jsr print_space jsr print_4hex ; year ldaa #'- jsr console_output jsr print_2hex ; month ldaa #'- jsr console_output jsr print_2hex ; day jsr print_space jsr print_2hex ; hour ldaa #': jsr console_output jsr print_2hex ; minute ldaa #': jsr console_output jsr print_2hex ; second jsr print_space jsr print_string ; filename ;;; Check if entry is a directory and print trailing '/' ldx xtemp ldaa ,x bita #$40 beq cmd_dir_print2 ldaa #'/ jsr console_output cmd_dir_print2: jsr print_crlf rts ;;; ;;; Command 'cd' (change work directory) ;;; command_cd: ;;; Move line_pointer to first argument (directory name) ldx line_pointer jsr str_skp_next stx line_pointer stx arg_pointer ldaa ,x bne *+4 bra cmd_cd_root ;;; Check if it's a "cd root" ldx ,x cpx #$2f00 ; is argument '/'? bne cmd_cd_chk cmd_cd_root: ldx root_dir_beg ; set work dir to root dir stx wrk_dir_beg ldx root_dir_end stx wrk_dir_end ldaa #'/ staa wrk_path ; set directory path to root clra staa wrk_path+1 ; end of string jmp prompt ;;; Check if entry exists cmd_cd_chk: jsr find_file_entry beq *+5 jmp error_not_found stx xtemp ;;; Verify that entry is a directory and readable ldaa ,x bita #$40 bne *+5 jmp error_denied bita #$01 bne *+5 jmp error_denied ;;; Enter directory ldd 1,x std wrk_dir_beg ldd 3,x std wrk_dir_end ;;; Add directory to current path ldx #wrk_path cmd_cd_loop1: ldaa ,x beq cmd_cd_cpy inx bra cmd_cd_loop1 cmd_cd_cpy: stx xtemp2 ldx xtemp ldab #16 ; beginning of entry name abx stx xtemp cmd_cd_loop2: ldx xtemp ldaa ,x beq cmd_cd_cpy_end inx stx xtemp ldx xtemp2 staa ,x inx cpx #wrk_path_end beq cmd_cd_cpy_end stx xtemp2 bra cmd_cd_loop2 cmd_cd_cpy_end: ldx xtemp2 ldaa #'/ staa ,x inx clr ,x jmp prompt ;;; ;;; Command 'mkdir' ;;; command_mkdir: ;;; Move line_pointer to first argument (directory name) ldx line_pointer jsr str_skp_next stx line_pointer stx arg_pointer ldaa ,x bne *+5 jmp error_argument ;;; Check if valid file name ldx arg_pointer jsr validate_filename_arg bcs *+5 jmp error_argument ;;; Check if entry already exists jsr find_file_entry bne *+5 jmp error_exist ;;; Locate a free directory entry for the directory jsr find_free_entry ; find empty entry in current directory beq *+5 ; any free space left? jmp error_no_space ;;; Allocate space for directory ldaa #8 ; allocate 8 sectors jsr alloc beq *+5 ; enough free space left on volume for a directory? jmp error_no_space jsr find_free_entry ; find empty entry in current directory (same as last time) beq *+5 jmp error_no_space ;;; Setup new directory entry ldaa #$C3 ; set entry to status "in use directory" and XWR staa ,x ldd cur_file_beg ; set entry start sector std 1,x ldd cur_file_end ; set entry end sector std 3,x clr 5,x ; write directory entry size (8*512bytes = 4096bytes = 0x1000) clr 6,x ldd #$1000 std 7,x ;;; TODO: write time stamp to entry ldd arg_pointer std line_pointer ldab #9 ; go to start of time stamp abx jsr rtc_get_time ; write time stamp ldab #7 ; go to start of entry file name abx ldab #31 cmd_mkdir_name: ; copy name char by char jsr line_pull_char tsta beq cmd_mkdir_name_e cmpa #$20 beq cmd_mkdir_name_e staa ,x inx decb bne cmd_mkdir_name cmd_mkdir_name_e: clra staa ,x ; terminate name sting ;;; Write back directory sector ldx #sector_buffer jsr write_sector beq *+5 jmp error_general ;;; Init allocated/new directory sectors ldx #sector_buffer ; clear sector buffer cmd_mkdir_init1: clr ,x inx cpx #sector_buffer+511 bls cmd_mkdir_init1 ldx cur_file_end ; load allocated/new directory start sector stx media_addr_1 cmd_mkdir_init2: ; write allocated/new directory sectors ldx #sector_buffer jsr write_sector beq *+5 jmp error_general ldx media_addr_1 dex stx media_addr_1 cpx cur_file_beg bhi cmd_mkdir_init2 beq cmd_mkdir_init2 cmd_mkdir_end: jmp prompt ;;; ;;; Command 'save' ;;; command_save: ;;; Move line_pointer to first argument (file name) ldx line_pointer jsr str_skp_next stx line_pointer stx arg_pointer ldaa ,x bne *+5 jmp error_argument ;;; Check if valid file name ldx arg_pointer jsr validate_filename_arg bcs *+5 jmp error_argument ;;; Check if file already exists jsr find_file_entry bne *+5 jmp error_exist ;;; Read begin address ldx arg_pointer ; move line_pointer to second argument jsr str_skp_next stx line_pointer ldaa ,x bne *+5 jmp error_argument jsr line_pull_4hex bcs *+5 jmp error_argument ldx xtemp stx cur_file_saddr * ldx #xtemp ; DEBUG * jsr print_4hex ; DEBUG ;;; Read end address ldx line_pointer ; move line_pointer to third argument jsr str_skp_next stx line_pointer ldaa ,x bne *+5 jmp error_argument jsr line_pull_4hex bcs *+5 jmp error_argument ldx xtemp stx cur_file_eaddr * ldx #xtemp ; DEBUG * jsr print_4hex ; DEBUG ;;; Locate a free directory entry for the file jsr find_free_entry ; find empty entry in current directory beq *+5 ; any free space left? jmp error_no_space ;;; Determine number of sectors needed for the file ldaa #$01 staa temp ; sector counter (start at one sector) ldd cur_file_eaddr ; calculate file size subd cur_file_saddr cmd_save_loop1: cmpa #$01 bls cmd_save_loop1_done ; calculation done? subd #512 inc temp bra cmd_save_loop1 cmd_save_loop1_done: ;;; Allocate sectors for the file * ldx #temp ; DEBUG * jsr print_2hex ; DEBUG * jsr print_crlf ; DEBUG ldaa temp ; number of sectors to allocate jsr alloc beq *+5 ; enough free space left on volume for file? jmp error_no_space ;;; Setup new directory entry jsr find_free_entry ; find the empty entry in current directory again beq *+5 jmp error_no_space ldaa #$A3 ; set entry to status "in use file" and -WR staa ,x ldd cur_file_beg ; set file start sector std 1,x ldd cur_file_end ; set file end sector std 3,x ldd cur_file_eaddr ; set file size subd cur_file_saddr addd #1 bcc cmd_save_size_lsb inc 6,x cmd_save_size_lsb: std 7,x ldd arg_pointer ; set file name std line_pointer ldab #9 ; go to start of time stamp abx jsr rtc_get_time ; write time stamp ldab #7 ; go to start of entry file name abx ldab #31 cmd_save_name: ; copy name into file entry jsr line_pull_char tsta beq cmd_save_name_done cmpa #$20 beq cmd_save_name_done staa ,x inx decb bne cmd_save_name cmd_save_name_done: clra staa ,x ; terminate name sting ;;; Write back directory sector ldx #sector_buffer jsr write_sector beq *+5 jmp error_general ;;; Print information ldx #txt_saving jsr print_string ldx #cur_file_saddr jsr print_4hex ldaa #'- jsr console_output ldx #cur_file_eaddr jsr print_4hex jsr print_crlf ;;; Write file data to data sectors ldx cur_file_beg ; set first sector to write stx media_addr_1 cmd_save_data_loop: jsr sector_buffer_in ; copy data to buffer ldx #sector_buffer jsr write_sector beq *+5 jmp error_general ldx media_addr_1 cpx cur_file_end ; all data sectors written? beq cmd_save_complete inx stx media_addr_1 bra cmd_save_data_loop cmd_save_complete: jmp prompt ;;; ;;; Command 'load' ;;; command_load: ;;; Move line_pointer to file name (first argument) ldx line_pointer jsr str_skp_next stx line_pointer stx arg_pointer ldaa ,x bne *+5 jmp error_argument ;;; Check if valid file name jsr validate_filename_arg bcs *+5 jmp error_argument ;;; Read load address ldx arg_pointer ; move line_pointer to second argument jsr str_skp_next stx line_pointer ldaa ,x bne *+5 jmp error_argument jsr line_pull_4hex bcs *+5 jmp error_argument ldx xtemp stx cur_file_saddr * ldx #xtemp ; DEBUG * jsr print_4hex ; DEBUG ;;; Check if file exists ldx arg_pointer ; move line_pointer to first argument stx line_pointer jsr find_file_entry ; lookup directory entry beq *+5 jmp error_not_found ;;; Read directory entry ldaa ,x bita #$01 ; verify that entry is readable bne *+5 jmp error_denied ldd 1,x ; get file start sector std cur_file_beg ldd 3,x ; get file end sector std cur_file_end ;;; Calculate file memory range ldd 7,x subd #1 addd cur_file_saddr std cur_file_eaddr ;;; Print information ldx #txt_loading jsr print_string ldx #cur_file_saddr jsr print_4hex ldaa #'- jsr console_output ldx #cur_file_eaddr jsr print_4hex jsr print_crlf ;;; Read file data sectors ldx cur_file_beg ; set first sector to read stx media_addr_1 clrb ; no byte skip when reading buffer cmd_load_data_loop: ldx #sector_buffer jsr read_sector beq *+5 jmp error_general jsr sector_buffer_out ; copy data from buffer ldx media_addr_1 cpx cur_file_end ; all data sectors written? beq cmd_load_complete inx stx media_addr_1 bra cmd_load_data_loop cmd_load_complete: jmp prompt ;;; ;;; Command 'run' ;;; command_run: ;;; Check for file in work dir ldx wrk_dir_beg stx cur_dir_beg ldx wrk_dir_end stx cur_dir_end ldx line_pointer jsr str_skp_next stx line_pointer stx arg_pointer jsr find_file_entry beq *+5 jmp error_not_found ldd arg_pointer std line_pointer jmp execute_file ;;; ;;; Command 'delete' ;;; command_delete: ;;; Move line_pointer to file name (first argument) ldx line_pointer jsr str_skp_next stx line_pointer stx arg_pointer ldaa ,x bne *+5 jmp error_argument ;;; Check if valid file name jsr validate_filename_arg bcs *+5 jmp error_argument ;;; Check if file exists jsr find_file_entry ; lookup directory entry beq *+5 jmp error_not_found ldaa ,x ; get entry flags bita #$02 ; check if writable bne *+5 jmp error_denied bita #$40 beq cmd_del_do ; entry is a file - perform delete jsr check_dir_empty ; entry is a directory - check if empty beq *+5 jmp error_not_empty ldx arg_pointer ; retore line_pointer (point to file name) stx line_pointer jsr find_file_entry ; lookup directory entry again beq *+5 jmp error_not_found cmd_del_do: ldd 1,x ; file start sector std cur_file_beg ldd 3,x ; file end sector std cur_file_end ;;; Clear directory entry for file ldaa #64 cmd_del_clear_entry: clr ,x inx deca bne cmd_del_clear_entry ;;; write back directory sector ldx #sector_buffer jsr write_sector beq *+5 jmp error_general ;;; Free allocated sectors * ldx #cur_file_beg ; DEBUG * jsr print_4hex ; DEBUG * ldx #cur_file_end ; DEBUG * jsr print_4hex ; DEBUG * jsr print_crlf ; DEBUG jsr dealloc beq *+5 jmp error_general jmp prompt ;;; ;;; Command 'rename' ;;; command_rename: ;;; Move line_pointer to old file name (first argument) ldx line_pointer jsr str_skp_next stx line_pointer stx arg_pointer ldaa ,x bne *+5 jmp error_argument ;;; Check if valid file name jsr validate_filename_arg bcs *+5 jmp error_argument ;;; Move line_pointer to new file name (second argument) ldx arg_pointer jsr str_skp_next ldaa ,x bne *+5 jmp error_argument ;;; Check if valid file name jsr validate_filename_arg bcs *+5 jmp error_argument ;;; Check if new entry name exists ldx arg_pointer jsr str_skp_next stx line_pointer jsr find_file_entry bne *+5 jmp error_exist ;;; Check if old entry name exists ldx arg_pointer stx line_pointer jsr find_file_entry beq *+5 jmp error_not_found ;;; Check if file is writable ldaa ,x bita #$02 bne *+5 jmp error_denied ;;; Update directory entry name pshx ldx arg_pointer ; go to second argument jsr str_skp_next stx line_pointer pulx ldab #16 ; go to start of entry file name abx ldab #31 cmd_rename_name: ; copy name char by char jsr line_pull_char tsta beq cmd_rename_name_e cmpa #$20 beq cmd_rename_name_e staa ,x inx decb bne cmd_rename_name cmd_rename_name_e: clra staa ,x ; terminate name sting ;;; Write back directory sector ldx #sector_buffer jsr write_sector beq *+5 jmp error_general jmp prompt ;;; ;;; Command 'attr' ;;; command_attr: ;;; Move line_pointer to file name (first argument) ldx line_pointer jsr str_skp_next stx line_pointer stx arg_pointer ldaa ,x bne *+5 jmp error_argument ;;; Check if valid file name jsr validate_filename_arg bcs *+5 jmp error_argument ;;; Check if file exists jsr find_file_entry ; lookup directory entry beq *+5 jmp error_not_found stx xtemp ; save entry pointer ;;; Read attributes ldx arg_pointer jsr str_skp_next ldaa ,x bne *+5 jmp error_argument clrb ; attribute storage cmd_attr_arg_loop: ldaa ,x beq cmd_attr_arg_loop_done anda #$df ; to upper case cmpa #'R beq cmd_attr_set_r cmpa #'W beq cmd_attr_set_w cmpa #'X beq cmd_attr_set_x cmd_attr_arg_loop_next: inx bra cmd_attr_arg_loop cmd_attr_set_r: orab #$01 bra cmd_attr_arg_loop_next cmd_attr_set_w: orab #$02 bra cmd_attr_arg_loop_next cmd_attr_set_x: orab #$04 bra cmd_attr_arg_loop_next cmd_attr_arg_loop_done: ;;; Update directory entry ldx xtemp ldaa ,x anda #$F8 staa ,x orab ,x stab ,x ;;; Write back directory sector ldx #sector_buffer jsr write_sector beq *+5 jmp error_general jmp prompt ;;; ;;; Command 'touch' ;;; command_touch: ;;; Move line_pointer to file name (first argument) ldx line_pointer jsr str_skp_next stx line_pointer stx arg_pointer ldaa ,x bne *+5 jmp error_argument ;;; Check if valid file name jsr validate_filename_arg bcs *+5 jmp error_argument ;;; Check if entry exists ldx arg_pointer stx line_pointer jsr find_file_entry beq *+5 jmp error_not_found ;;; Check if file is writable ldaa ,x bita #$02 bne *+5 jmp error_denied ;;; Update directory entry ldab #9 ; go to start of time stamp abx jsr rtc_get_time ; write time stamp ;;; Write back directory sector ldx #sector_buffer jsr write_sector beq *+5 jmp error_general jmp prompt ;;; ;;; Command 'time' ;;; command_time: ;;; Verify checksum ldaa #$47 adda ram_time+0 adda ram_time+1 adda ram_time+2 adda ram_time+3 adda ram_time+4 adda ram_time+5 adda ram_time+6 cmpa ram_time+7 beq command_time_print jmp prompt command_time_print ldx #sector_buffer jsr rtc_get_time ldx #sector_buffer jsr print_4hex ldaa #'- jsr console_output jsr print_2hex ldaa #'- jsr console_output jsr print_2hex jsr print_space jsr print_2hex ldaa #': jsr console_output jsr print_2hex ldaa #': jsr console_output jsr print_2hex jsr print_crlf jmp prompt ;;; ;;; Command 'settime' ;;; command_settime: ;;; Read YEAR ldx line_pointer jsr str_skp_next stx line_pointer ldaa ,x bne *+5 jmp command_settime_format jsr line_pull_4hex bcs *+5 jmp command_settime_format ldx xtemp stx sector_buffer ;;; Read MONTH ldx line_pointer jsr str_skp_next stx line_pointer ldaa ,x bne *+5 jmp command_settime_format jsr line_pull_2hex bcs *+5 jmp command_settime_format ldaa temp staa sector_buffer+2 ;;; Read DAY ldx line_pointer jsr str_skp_next stx line_pointer ldaa ,x bne *+5 jmp command_settime_format jsr line_pull_2hex bcs *+5 jmp command_settime_format ldaa temp staa sector_buffer+3 ;;; Read HOURS ldx line_pointer jsr str_skp_next stx line_pointer ldaa ,x bne *+5 jmp command_settime_format jsr line_pull_2hex bcs *+5 jmp command_settime_format ldaa temp staa sector_buffer+4 ;;; Read MINUTES ldx line_pointer jsr str_skp_next stx line_pointer ldaa ,x bne *+5 jmp command_settime_format jsr line_pull_2hex bcs *+5 jmp command_settime_format ldaa temp staa sector_buffer+5 ;;; Read SECONDS ldx line_pointer jsr str_skp_next stx line_pointer ldaa ,x bne *+5 jmp command_settime_format jsr line_pull_2hex bcs *+5 jmp command_settime_format ldaa temp staa sector_buffer+6 * ldx #sector_buffer ; DEBUG * jsr print_4hex ; DEBUG * jsr print_2hex ; DEBUG * jsr print_2hex ; DEBUG * jsr print_2hex ; DEBUG * jsr print_2hex ; DEBUG * jsr print_2hex ; DEBUG ldx #sector_buffer jsr rtc_set_time ;;; Update ram_time + checksum ldaa 1,x staa ram_time+1 ldaa 2,x staa ram_time+2 ldaa 3,x staa ram_time+3 ldaa 4,x staa ram_time+4 ldaa 5,x staa ram_time+5 ldaa 6,x staa ram_time+6 ldaa #$47 adda ram_time+0 adda ram_time+1 adda ram_time+2 adda ram_time+3 adda ram_time+4 adda ram_time+5 adda ram_time+6 staa ram_time+7 jmp command_time command_settime_format: ldx #txt_help_settime jsr print_string jmp prompt ;;; ;;; Command 'monitor' ;;; command_monitor: jsr var_checksum_calc staa var_checksum jmp monitor_return ;;; ;;; Command 'help' ;;; command_help: ldx #txt_help jsr print_string clrb ldx #command_table stx xtemp command_help_loop: ldx xtemp tst ,x beq command_help_end ldx ,x jsr print_space jsr print_string incb bitb #3 bne *+5 jsr print_crlf ldx xtemp inx inx stx xtemp bra command_help_loop command_help_end: jsr print_crlf jmp prompt ;;; ;;; Find and allocate free sectors ;;; IN: Number of sectors to allocate in A ;;; OUT: Z set on OK ;;; Error code in A ;;; Beginning of sector sequence in cur_file_beg ;;; End of sector sequence in cur_file_end ;;; alloc: staa temp ldx volume_beg ; set up volume sector counter stx cur_file_beg ldx alloc_beg ; set up allocation start sector stx media_addr_1 alloc_sector_read: * ldx #media_addr_1 ; DEBUG * jsr print_4hex ; DEBUG ldx #sector_buffer ; read allocation sector jsr read_sector beq *+3 rts ;;; Find sector gap ldx #sector_buffer alloc_sector_find_gap: ldaa ,x beq alloc_sector_found_gap ; sector entry is free ldd cur_file_beg ; increase sector pointer addd #1 std cur_file_beg inx ; increse sector table pointer cpx #sector_buffer+512 beq alloc_sector_read_next bra alloc_sector_find_gap ;;; Free sector found - check gap length alloc_sector_found_gap: ldd cur_file_beg std cur_file_end alloc_sector_gap: ldaa ,x bne alloc_sector_gap_end ; sector entry not free - end of gap ldd cur_file_end ; increase sector pointer addd #1 std cur_file_end inx ; increase sector table pointer cpx #sector_buffer+512 beq alloc_sector_gap_end bra alloc_sector_gap alloc_sector_gap_end: ldd cur_file_end subd cur_file_beg tsta bne alloc_sector_gap_ok ; gap over 256 sectors? cmpb temp ; gap under 256 sectors but long enough? bhi alloc_sector_gap_ok beq alloc_sector_gap_ok ;;; Gap not log enough ldd cur_file_end std cur_file_beg bra alloc_sector_find_gap ; look for next gap ;;; Read next allocation sector alloc_sector_read_next: ldx media_addr_1 ; prepare for next sector inx cpx alloc_end bhi alloc_sectors_full stx media_addr_1 jmp alloc_sector_read alloc_sectors_full: ldaa #$F2 ; error - allocation failed rts ;;; Gap long enough found alloc_sector_gap_ok: ldd cur_file_end ; adjust sector pointer subd #1 std cur_file_end dex ; adjust sector table pointer ;;; Mark sectors in table ldd cur_file_end std cur_file_beg alloc_sector_table: inc ,x dec temp beq alloc_sector_table_done dex ldd cur_file_beg subd #1 std cur_file_beg bra alloc_sector_table alloc_sector_table_done: ldx #sector_buffer ; write back allocation sector jsr write_sector beq *+3 rts * jsr print_crlf ; DEBUG * ldx #cur_file_beg ; DEBUG * jsr print_4hex ; DEBUG * ldx #cur_file_end ; DEBUG * jsr print_4hex ; DEBUG * ldx #temp ; DEBUG * jsr print_2hex ; DEBUG * jsr print_crlf ; DEBUG ;;; Allocation OK clra rts ;;; ;;; Free allocated sectors ;;; IN: Start sector in cur_file_beg ;;; End sector in cur_file_end ;;; OUT: Z set on OK ;;; Error code in A ;;; dealloc: ;;; Calculate allocation sector ldd cur_file_beg ldx alloc_beg dealloc_calc1: cmpa #$01 ; D-reg under 511? bls dealloc_calc1_found subd #512 inx bra dealloc_calc1 ; is it the next allocation sector? ;;; Read allocation sector dealloc_calc1_found: std xtemp stx media_addr_1 ; prepare to read allocation sector ldx #sector_buffer jsr read_sector ; read allocation sector beq *+3 rts ;;; Find first sector in allocation table ldd xtemp ; retore D addd #sector_buffer ; D now points to first sector in table xgdx ;;; Calculate number of sectors to free ldd cur_file_end subd cur_file_beg ;;; Clear sectors in table dealloc_loop1: clr ,x tstb beq dealloc_loop1_done inx cpx #sector_buffer+511 bhi dealloc_error ; end of table error decb bra dealloc_loop1 ;;; Write back allocation sector dealloc_loop1_done: ldx #sector_buffer jsr write_sector ; write back allocation sector beq *+3 rts clra rts dealloc_error: ldaa #$F2 ; error - deallocation failed rts ;;; ;;; Find free directory entry ;;; OUT: Z set on OK ;;; A set to error code ;;; X points to entry in sector_buffer ;;; media_addr_[3:0] point to sector LBA ;;; find_free_entry: ldx cur_dir_beg find_free_loop1: cpx cur_dir_end ; compare with end of current directory bhi find_free_full ; end if we have looped through entire directory stx media_addr_1 ldx #sector_buffer jsr read_sector ; read next sector of directory beq *+3 rts ldx #sector_buffer find_free_loop2: ldaa ,x bne find_free_gonext clra ; no error code rts find_free_gonext: ldab #64 ; within current sector abx cpx #sector_buffer+448 bls find_free_loop2 ; goto next entry in sector ldx media_addr_1 inx bra find_free_loop1 ; goto next sector in directory find_free_full: ldaa #$F8 ; error - directory is full rts ;;; ;;; Find file in directory ;;; IN: file name pointed at by line_pointer ;;; OUT: Z set on OK ;;; A set to error code ;;; X points to entry in sector_buffer ;;; media_addr_[3:0] point to sector LBA ;;; find_file_entry: ldx line_pointer ; save line pointer for multiple compares stx xtemp ldx cur_dir_beg find_file_loop1: cpx cur_dir_end ; compare with end of current directory bhi find_file_not ; end if we have looped through entire directory stx media_addr_1 ldx #sector_buffer jsr read_sector ; read next sector of directory beq *+3 rts ldx #sector_buffer find_file_loop2: ldaa ,x beq find_file_gonext pshx ldab #16 ; go to start of file name abx ldd xtemp std line_pointer jsr string_compare pulx bne find_file_gonext clra ; no error code rts find_file_gonext: ldab #64 ; within current sector abx cpx #sector_buffer+448 bls find_file_loop2 ; goto next entry in sector ldx media_addr_1 inx bra find_file_loop1 ; goto next sector in directory find_file_not: ldaa #$F9 ; error - file not found rts ;;; ;;; Check if a directory entry is empty ;;; IN: X pointer to directory entry to check ;;; OUT: Z set if directory is empty ;;; A set to error code ;;; check_dir_empty: ldd 1,x std xtemp ; directory start sector ldd 3,x std media_addr_1 ; directiry end sector check_dir_empty1: ;;; Read directory sector ldx #sector_buffer jsr read_sector beq *+3 rts ;;; Check if sector is empty (all bytes == $00) ldx #sector_buffer check_dir_empty2: tst ,x bne check_dir_empty_fail inx cpx #sector_buffer+511 bls check_dir_empty2 ;;; Have we cehcked all sectors? ldx media_addr_1 dex stx media_addr_1 cpx xtemp bhi check_dir_empty1 beq check_dir_empty1 ;;; If we get here all sectors in directoru are empty clra rts check_dir_empty_fail: ldaa #$F5 ; error - directory not empty rts ;;; ;;; Open volume ;;; open_volume: ;;; Read volume header clr media_addr_3 clr media_addr_2 clr media_addr_1 clr media_addr_0 ldx #sector_buffer jsr read_sector ; read first sector of drive bne open_volume_error ;;; Verify magic word + version ldx vol_magic cpx #$4D43 ; "MC" bne open_volume_error ldx vol_magic+2 cpx #$4653 ; "FS" bne open_volume_error ldaa vol_version cmpa #$02 ; version 2 bne open_volume_error ;;; Extract root dir info ldx vol_beg stx volume_beg ; pointer to beginning of volume ldx vol_end stx volume_end ; pointer to end of volume ldx vol_root_beg stx root_dir_beg ; pointer to beginning of root dir stx wrk_dir_beg ; pointer to beginning of work dir ldx vol_root_end stx root_dir_end ; pointer to end of root dir stx wrk_dir_end ; pointer to end of work dir ldx vol_alloc_beg stx alloc_beg ; pointer to beginning of allocation table ldx vol_alloc_end stx alloc_end ; pointer to end of allocation table ;;; Setup path string ldaa #'/ staa wrk_path ; set directory path to root clra staa wrk_path+1 ; end of string clra rts open_volume_error: ldaa #$F1 ; ID not found rts ;;; ;;; Routine for copying file data to sector buffer ;;; Copy stops when sector is full or cur_file_eaddr reached ;;; IN: file range in cur_file_saddr & cur_file_eaddr ;;; OUT: data in file_buffer ;;; updated cur_file_saddr ;;; sector_buffer_in: ldx #sector_buffer stx xtemp ; sector buffer pointer sector_buffer_in_loop: ldx cur_file_saddr ldaa ,x inx stx cur_file_saddr ldx xtemp staa ,x inx stx xtemp cpx #sector_buffer+512 ; end of sector fuffer? beq sector_buffer_in_done ldx cur_file_saddr cpx cur_file_eaddr ; end of file? bhi sector_buffer_in_done bra sector_buffer_in_loop sector_buffer_in_done: rts ;;; ;;; Routine for copying file data from sector buffer ;;; Copy stops at end of sector or cur_file_eaddr reached ;;; IN: file range in cur_file_saddr & cur_file_eaddr ;;; leading bytes to skip in B acc (set to 0 for no skip) ;;; OUT: data in memory ;;; updated cur_file_saddr ;;; sector_buffer_out: ldx #sector_buffer abx ; bytes to skip clrb ; skip no more stx xtemp ; sector buffer pointer sector_buffer_out_loop: ldx xtemp ldaa ,x inx stx xtemp ldx cur_file_saddr staa ,x cpx cur_file_eaddr ; end of file? beq sector_buffer_out_done inx stx cur_file_saddr ldx xtemp cpx #sector_buffer+512 ; end of sector buffer? bne sector_buffer_out_loop sector_buffer_out_done: rts ;;; ;;; Execute file at file pointer at X ;;; execute_file: ldaa ,x anda #$A5 ; check if readable and executable file cmpa #$A5 beq *+5 jmp error_denied ldd 1,x ; get file start sector std cur_file_beg std media_addr_1 ldd 3,x ; get file end sector std cur_file_end ldd 7,x ; get file size subd #1 std cur_file_eaddr ; store in eaddr for now ;;; Load first sector of file ldx #sector_buffer jsr read_sector beq *+5 jmp error_general ldx #sector_buffer ldd ,x ; read magic number cmpa #$47 ; check for $47 beq *+5 jmp error_not_exec tstb ; check for $00 beq *+5 jmp error_not_exec ldd 2,x ; read load address std cur_file_saddr ldd 4,x ; read entry address std xtemp2 ldd cur_file_eaddr ; calculate end address addd cur_file_saddr subd #2 std cur_file_eaddr * ldx #cur_file_saddr ; DEBUG * jsr print_4hex ; DEBUG * jsr print_space ; DEBUG * ldx #cur_file_eaddr ; DEBUG * jsr print_4hex ; DEBUG ;;; Load file into memory ldab #6 ; skip first 6 bytes of file (exec header) execute_file_loop: ldx #sector_buffer jsr read_sector beq *+5 jmp error_general jsr sector_buffer_out ldx media_addr_1 cpx cur_file_end ; all data sectors written? beq execute_file_loop_ok inx stx media_addr_1 bra execute_file_loop execute_file_loop_ok: ;;; Execute loaded file * jsr print_crlf ; DEBUG jsr var_checksum_calc ; generate variable checksum staa var_checksum ;;; Load argument pointer and jump to program ldd line_pointer ldx xtemp2 pshx ; push program start address to stack ldx line_pointer ldd line_pointer rts ; jump to program address ;;; ;;; Read sector at media_addr_[3:0] store at X ;;; Z set on OK read. Error code in A ;;; read_sector: jsr cf_wait jsr cf_set_lba ldaa #1 staa cf_reg_2 ; number of sectors to read ldaa #cf_cmd_read staa cf_reg_7 ; issue read command jmp cf_read_data ; read data routine rts ;;; ;;; Write sector at media_addr_[3:0] stored at X ;;; Z set on OK write. Error code in A ;;; write_sector: jsr cf_wait jsr cf_set_lba ldaa #1 staa cf_reg_2 ; number of sectors to write ldaa #cf_cmd_write staa cf_reg_7 ; issue write command jmp cf_write_data ; write data routine rts ;;; ;;; Initialize CF ;;; cf_init: ldaa #cf_cmd_reset ; reset command staa cf_reg_7 jsr cf_wait ldaa #$E0 ; LBA3=0, MASTER, MODE=LBA staa cf_reg_6 ldaa #$01 ; 8-bit transfers staa cf_reg_1 ldaa #$EF ; set-feature command staa cf_reg_7 jsr cf_wait rts ;;; ;;; Wait for CF ready ;;; cf_wait: ldaa cf_reg_7 anda #$80 ; mask out BUSY bit cmpa #$00 bne cf_wait rts ;;; ;;; Check for CF error ;;; Z set on OK ;;; Error code in A ;;; bit 7 - Bad block detected ;;; bit 6 - Uncorrectable data error ;;; bit 5 - Media changed ;;; bit 4 - ID not found ;;; bit 3 - Aborted command ;;; bit 2 - Media change requested ;;; bit 1 - Track 0 not found ;;; bit 0 - Addressmask not found ;;; cf_check_error: ldaa cf_reg_7 anda #$01 ; mask out ERROR bit cmpa #0 beq cf_no_error ldaa cf_reg_1 rts cf_no_error: clra rts ;;; ;;; CF set LBA ;;; cf_set_lba: ldaa media_addr_0 ; LBA byte 0 staa cf_reg_3 ldaa media_addr_1 ; LBA byte 1 staa cf_reg_4 ldaa media_addr_2 ; LBA byte 2 staa cf_reg_5 ldaa media_addr_3 ; LBA byte 3 anda #%00001111 ; mask out LBA bits oraa #%11100000 ; set mode LBA and master device staa cf_reg_6 rts ;;; ;;; Read data from CF ;;; Z set on OK ;;; error code in A ;;; cf_read_data: jsr cf_wait ldaa cf_reg_7 anda #$08 ; mask out DRQ bit cmpa #$08 bne cf_read_data_e ldaa cf_reg_0 ; read data byte staa ,X inx jsr cf_check_error beq cf_read_data rts cf_read_data_e: clra rts ;;; ;;; Write data to CF ;;; Z set on OK ;;; error code in A ;;; cf_write_data: jsr cf_wait ldaa cf_reg_7 anda #$08 ; mask out DRQ bit cmpa #$08 bne cf_write_data_e ldaa ,X staa cf_reg_0 ; write data byte inx jsr cf_check_error beq cf_write_data rts cf_write_data_e: clra rts ;;; ;;; Get ISO 8601 time stamp from RTC and store at X ;;; rtc_get_time: psha pshb rtc_get_time_loop: ;;; Loop until two identical time stamps have been read jsr rtc_get_time_sample tstb bne rtc_get_time_loop pulb pula rts rtc_get_time_sample: clrb ;;; Read SECONDS pshx ldx #rtc_reg_s1 jsr rtc_read_8bit pulx cmpa 6,x beq *+3 incb staa 6,x ;;; Read MINUTES pshx ldx #rtc_reg_mi1 jsr rtc_read_8bit pulx anda #$7f cmpa 5,x beq *+3 incb staa 5,x ;;; Read HOURS pshx ldx #rtc_reg_h1 jsr rtc_read_8bit pulx anda #$3f cmpa 4,x beq *+3 incb staa 4,x ;;; Read DAY pshx ldx #rtc_reg_d1 jsr rtc_read_8bit pulx anda #$3f cmpa 3,x beq *+3 incb staa 3,x ;;; Read MONTH pshx ldx #rtc_reg_mo1 jsr rtc_read_8bit pulx anda #$1f cmpa 2,x beq *+3 incb staa 2,x ;;; Read YEAR pshx ldx #rtc_reg_y1 jsr rtc_read_8bit pulx cmpa 1,x beq *+3 incb staa 1,x ;;; Read CENTURY ldaa ram_time+0 staa 0,x rts ;;; Helper funtion to assemble two RTC nibs to one byte rtc_read_8bit: pshb ldaa 1,x asla asla asla asla ldab 0,x andb #$0f aba pulb rts ;;; ;;; Set ISO 8601 time stamp to RTC stored at X ;;; rtc_set_time: ;;; Stop RTC ldaa #$07 staa rtc_reg_cf clra staa rtc_reg_cd ;;; Set CENTURY ldaa 0,x staa ram_time+0 ;;; Set YEAR ldaa 1,x pshx ldx #rtc_reg_y1 jsr rtc_write_8bit pulx ;;; Set MONTH ldaa 2,x pshx ldx #rtc_reg_mo1 jsr rtc_write_8bit pulx ;;; Set DAY ldaa 3,x pshx ldx #rtc_reg_d1 jsr rtc_write_8bit pulx ;;; Set HOURS ldaa 4,x pshx ldx #rtc_reg_h1 jsr rtc_write_8bit pulx ;;; Set MINUTES ldaa 5,x pshx ldx #rtc_reg_mi1 jsr rtc_write_8bit pulx ;;; Set SECONDS ldaa 6,x pshx ldx #rtc_reg_s1 jsr rtc_write_8bit pulx ;;; Start RTC ldaa #$04 staa rtc_reg_cf rts ;;; Helper funtion to assemble two RTC nibs to one byte rtc_write_8bit: pshb tab lsra lsra lsra lsra staa 1,x andb #$0f stab 0,x pulb rts ;;; ;;; Update ram_time ;;; update_ram_time: ldaa #$47 adda ram_time+0 adda ram_time+1 adda ram_time+2 adda ram_time+3 adda ram_time+4 adda ram_time+5 adda ram_time+6 cmpa ram_time+7 bne ram_time_fail ldx #ram_time jsr rtc_get_time ldaa #$47 adda ram_time+0 adda ram_time+1 adda ram_time+2 adda ram_time+3 adda ram_time+4 adda ram_time+5 adda ram_time+6 staa ram_time+7 rts ram_time_fail: ldx #txt_time_not_set jsr print_string rts ;;; ;;; Program variables ;;; org $bc00 ; beginning of OS RAM beg_of_vars: equ * ; beginning of variables media_addr_3: rmb 1 ; sector address MSB media_addr_2: rmb 1 ; sector address media_addr_1: rmb 1 ; sector address media_addr_0: rmb 1 ; sector address LSB volume_beg: rmb 2 ; first sector of volume volume_end: rmb 2 ; last sector of volume alloc_beg: rmb 2 ; first sector of allocation table alloc_end: rmb 2 ; last sector of allocation table root_dir_beg: rmb 2 ; first sector of root directory root_dir_end: rmb 2 ; last sector of root directory wrk_dir_beg: rmb 2 ; first sector of work directory wrk_dir_end: rmb 2 ; last sector of work directory wrk_path: rmb 128 ; path to work directory wrk_path_end: equ *-1 ; end of work dir path cur_dir_beg: rmb 2 ; first sector of current directory cur_dir_end: rmb 2 ; last sector of current directory cur_file_beg: rmb 2 ; first sector of current file cur_file_end: rmb 2 ; last sector of current file cur_file_saddr: rmb 2 ; first address of current file cur_file_eaddr: rmb 2 ; last address of current file line_buffer: rmb 64 ; console line buffer line_end: equ *-1 ; end of console line buffer line_pointer: rmb 2 ; pointer whitin line_buffer arg_pointer: rmb 2 ; pointer to command argument ext_fcb: rmb 2 ; pointer or external file control block temp: rmb 1 ; temporary storage xtemp: rmb 2 ; temporary storage xtemp2: rmb 2 ; temporary storage end_of_vars: equ *-1 ; end of variables var_checksum: rmb 1 ; variable checksum ;;; ;;; Sector buffer ;;; sector_buffer: rmb 512 ;;; ;;; RAM time stamp with checksum ;;; org $bff8 ram_time: rmb 7 ; ISO 8601 time stamp rmb 1 ; Time stamp checksum ;;; ;;; Volume header pointers ;;; org sector_buffer vol_magic: rmb 4 ; file system ID = "MCFS" vol_version: rmb 1 ; file system version = $02 vol_beg: rmb 2 ; first sector of partition vol_end: rmb 2 ; last sector of partition vol_id: rmb 2 ; volume ID vol_name: rmb 32 ; volume label (zero terminated) vol_alloc_beg: rmb 2 ; first sector of allocation table vol_alloc_end: rmb 2 ; last sector of allocation table (up to 16 sectors) vol_root_beg: rmb 2 ; first sector of root dir vol_root_end: rmb 2 ; last sector of root dir ;;; # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ;;; Definition of volume header - first sector of file system ;;; ;;; 4 byte - file system magic ID = "MCFS" ;;; 1 byte - file system version = $02 ;;; 2 byte - first sector of partition ;;; 2 byte - last sector of partition ;;; 2 byte - volume ID ;;; 32 byte - volume label (zero terminated) ;;; 2 byte - first sector of allocation table ;;; 2 byte - last sector of allocation table ;;; 2 byte - first sector of root dir ;;; 2 byte - last sector of root dir ;;; # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ;;; Definition of directry entry - 64 bytes each / 8 per sector (only 48 bytes used, rest is zero) ;;; (Timestamp is ISO 8601) ;;; ;;; 1 byte - flags [INUSE DIR FILE 0 0 0 X W R] (initially set to $00 for empty entry. DIR & FILE can not both be set) ;;; 2 byte - first sector ;;; 2 byte - last sector ;;; 4 byte - size ;;; 2 byte - date year (BCD) ;;; 1 byte - date month (BCD) ;;; 1 byte - date day (BCD) ;;; 1 byte - time hours (BCD) ;;; 1 byte - time minutes (BCD) ;;; 1 byte - time seconds (BCD) ;;; 32 byte - name (zero terminated) ;;; 16 byte - reserved for future use, set to zero ;;; # # # # # # # # # # # # # # # # # # # ;;; Definition of executable file format ;;; ;;; 2 byte - magic number = $4700 ;;; 2 byte - load address ;;; 2 byte - entry address ;;; n byte - program data ;;; # # # # # # # # # # # # # # # # ;;; Definition of command blocks ;;; ;;; # # # # # # # # # # ;;; Check file entry ;;; ;;; 1 byte - command $01 ;;; 1 byte - error code ;;; 2 byte - pointer to null terminated file name ;;; 1 byte - file flags [INUSE DIR FILE 0 0 0 X W R] ;;; 2 byte - number of sectors ;;; 4 byte - file size ;;; 2 byte - entry date year ;;; 1 byte - entry date month ;;; 1 byte - entry date day ;;; 1 byte - entry time hours ;;; 1 byte - entry time minutes ;;; 1 byte - entry time seconds ;;; ;;; # # # # # # # # # # ;;; Rename file ;;; ;;; 1 byte - command $07 ;;; 1 byte - error code ;;; 2 byte - pointer to null terminated file name - from ;;; 2 byte - pointer to null terminated file name - to ;;; ;;; # # # # # # # # # # ;;; Delete file ;;; ;;; 1 byte - command $08 ;;; 1 byte - error code ;;; 2 byte - pointer to null terminated file name ;;; ;;; # # # # # # ;;; Load file ;;; ;;; 1 byte - command $10 ;;; 1 byte - error code ;;; 2 byte - pointer to null terminated file name ;;; 2 byte - destination address ;;; ;;; # # # # # # # # # # ;;; Load file sector ;;; ;;; 1 byte - command $11 ;;; 1 byte - error code ;;; 2 byte - pointer to null terminated file name ;;; 2 byte - destination address ;;; 2 byte - sector to load (from zero to end of file) ;;; ;;; # # # # # # ;;; Save file ;;; ;;; 1 byte - command $20 ;;; 1 byte - error code ;;; 2 byte - pointer to null terminated file name ;;; 2 byte - begin address ;;; 2 byte - end address ;;; 1 byte - file flags [0 0 0 0 0 0 X W R] ;;; ;;; # # # # # # # # # # ;;; (Save file sector) ;;; ;;; 1 byte - command $21 ;;; 1 byte - error code ;;; 2 byte - pointer to null terminated file name ;;; 2 byte - source address ;;; 2 byte - sector to save (from zero to end of file) ;;;