Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
; SD Card and FAT32 development testbed
; contains reusable functions that can be the basis for enhancing the monitor
; done by sy2002 in June .. August 2016
;
; November 2016 .. December 2016: enhanced by new test case and fixed
; "read multiple files in parallel" bug, so that for example the test program
; c/test_programs/fread_basic.c works
#include "../dist_kit/sysdef.asm"
#include "../dist_kit/monitor.def"
.ORG 0x8000
MOVE STR_TITLE, R8
SYSCALL(puts, 1)
; Initialize Variables
MOVE DIR_FLAGS, R8
MOVE FAT32$FA_DEFAULT, @R8
MOVE PRINT_FLAGS, R8
MOVE FAT32$PRINT_DEFAULT, @R8
; Reset SD Card
MOVE STR_RESET, R8
SYSCALL(puts, 1)
RSUB SD$RESET, 1
RSUB ERR_CHECK, 1
MOVE STR_OK, R8
SYSCALL(puts, 1)
SYSCALL(crlf, 1)
; SD registers
MOVE IO$SD_ADDR_LO, R0
MOVE IO$SD_ADDR_HI, R1
MOVE IO$SD_DATA_POS, R2
MOVE IO$SD_DATA, R3
MOVE IO$SD_ERROR, R4
MOVE IO$SD_CSR, R5
; Detect card type
MOVE STR_CARDTYPE, R8
SYSCALL(puts, 1)
MOVE @R5, R7 ; read CSR
SHR 12, R7 ; bits 13 .. 12 are relevant
RBRA DCT_1, !Z ; 00 = unknown card / error
MOVE STR_CT_NA, R8
RBRA DCT_DONE, 1
DCT_1 CMP R7, SD$CT_SD_V1 ; 01 = SD V1
RBRA DCT_2, !Z
MOVE STR_CT_SDV1, R8
RBRA DCT_DONE, 1
DCT_2 CMP R7, SD$CT_SD_V2 ; 10 = SD V2
RBRA DCT_3, !Z
MOVE STR_CT_SDV2, R8
RBRA DCT_DONE, 1
DCT_3 CMP R7, SD$CT_SDHC ; 11 = SDHC
MOVE STR_CT_SDHC, R8
RBRA DCT_DONE, 1
MOVE STR_CT_INCONS, R8 ; inconsistent value
; (hardware/vhdl/asm mismatch)
DCT_DONE SYSCALL(puts, 1) ; print card type
; perform register write/read-back checks
MOVE STR_REGCHK_T, R8
SYSCALL(puts, 1)
MOVE 0, R11 ; deactivate do not write mode
MOVE R0, R8 ; check ADDR_LO
MOVE 0x2309, R9
MOVE STR_REGCHK_AL, R10
RSUB REG_CHECK, 1
MOVE R1, R8 ; check ADDR_HI
MOVE 0xABA0, R9
MOVE STR_REGCHK_AH, R10
RSUB REG_CHECK, 1
MOVE R2, R8 ; check DATA_POS
MOVE 0x4505, R9
MOVE STR_REGCHK_DP, R10
RSUB REG_CHECK, 1
MOVE R3, R8 ; check DATA
MOVE 0x0076, R9 ; (is an 8-bit register)
MOVE STR_REGCHK_DTA, R10
RSUB REG_CHECK, 1
MOVE 1, R11 ; activate do not write mode
MOVE R4, R8 ; ERROR is read only
MOVE 0x1100, R9 ; and must be 0x1100 (idle)
MOVE STR_REGCHK_ER, R10
RSUB REG_CHECK, 1
MOVE 2, R11 ; do not write mode and mask
MOVE R5, R8 ; card type, but all the other
MOVE 0x0000, R9 ; bits of CSR must be zero
MOVE STR_REGCHK_CS, R10
RSUB REG_CHECK, 1
; main menu
MAIN_MENU MOVE STR_MEN_TITLE, R8
SYSCALL(puts, 1)
MOVE STR_MEN_1, R8
SYSCALL(puts, 1)
MAIN_MRR SYSCALL(getc, 1)
CMP R8, '1'
RBRA IA_DUMP, Z
CMP R8, '2'
RBRA MNT_SD, Z
CMP R8, '3'
RBRA END_PROGRAM, Z
RBRA MAIN_MRR, 1
;=============================================================================
; Mount partition 1 as FAT32, perform various tests and enter interactive mode
;=============================================================================
MNT_SD MOVE STR_MNT_TILE, R8 ; print testcase title
SYSCALL(puts, 1)
MOVE DEVICE_HANDLE, R8 ; device handle
MOVE 1, R9 ; partition #1
RSUB FAT32$MOUNT_SD, 1 ; mount device
MOVE R9, R8
RSUB ERR_CHECK, 1
MOVE STR_OK, R8
SYSCALL(puts, 1)
SYSCALL(crlf, 1)
MOVE STR_MNT_FSSTART, R8 ; show fs start address
MOVE FAT32$DEV_FS_HI, R9
MOVE FAT32$DEV_FS_LO, R10
MOVE 1, R11
RSUB OUTPUT_DW, 1
MOVE STR_MNT_FATSTRT, R8 ; show FAT start address
MOVE FAT32$DEV_FAT_HI, R9
MOVE FAT32$DEV_FAT_LO, R10
RSUB OUTPUT_DW, 1
MOVE STR_MNT_CLSSTRT, R8 ; show clusters start address
MOVE FAT32$DEV_CLUSTER_HI, R9
MOVE FAT32$DEV_CLUSTER_LO, R10
RSUB OUTPUT_DW, 1
MOVE STR_MNT_SPC, R8 ; show sectors per cluster
MOVE FAT32$DEV_SECT_PER_CLUS, R9
MOVE 0, R11
RSUB OUTPUT_DW, 1
MOVE STR_MNT_ROOT, R8 ; show 1st cluster of root dir
MOVE FAT32$DEV_RD_1STCLUS_HI, R9
MOVE FAT32$DEV_RD_1STCLUS_LO, R10
MOVE 1, R11
RSUB OUTPUT_DW, 1
MOVE STR_MNT_ACTIVE, R8 ; show 1st cluster active dir
MOVE FAT32$DEV_AD_1STCLUS_HI, R9
MOVE FAT32$DEV_AD_1STCLUS_LO, R10
RSUB OUTPUT_DW, 1
SYSCALL(crlf, 1)
MOVE STR_INTERACTIVE, R8
SYSCALL(puts, 1)
SYSCALL(crlf, 1)
; show prompt and read command
NEW_PROMPT MOVE STR_PROMPT, R8
SYSCALL(puts, 1)
MOVE STRBUF, R8
SYSCALL(gets, 1)
SYSCALL(crlf, 1)
; separate command from the rest of the string
MOVE ' ', R9
RSUB SPLIT, 1
MOVE R9, R0 ; R0 = restore stack offset
MOVE SP, R1 ; R1 = string array
CMP R8, 0 ; nothing entered
RBRA INPUT_NP, Z
MOVE R1, R8 ; command array
ADD 1, R8 ; skip size
SYSCALL(str2upper, 1) ; command string to upper case
; check for "dir" command
MOVE STR_IB_DIR, R9
SYSCALL(strcmp, 1)
CMP R10, 0 ; dir command?
RBRA INPUT_N1, !Z ; no: try next
RSUB DIR, 1 ; yes: perform dir command
RBRA INPUT_NP, 1 ; new prompt
; check for "cd" command
INPUT_N1 MOVE STR_IB_CD, R9
SYSCALL(strcmp, 1)
CMP R10, 0 ; cd command?
RBRA INPUT_N2, !Z ; no: try next
MOVE 2, R5 ; lengh of command "CD"
RSUB _INPUT_CHKPAR, 1 ; check for parameter
MOVE DEVICE_HANDLE, R8 ; device handle for cd command
MOVE STRBUF, R9
ADD 3, R9 ; skip 3 chars ("cd ")
MOVE R9, R7 ; remember path
XOR R10, R10
RSUB FAT32$CD, 1 ; change directory
MOVE R9, R8
CMP R8, FAT32$ERR_DIRNOTFOUND ; path not found?
RBRA _INPUTN1EC, !Z ; other error or no error
MOVE STR_IB_CD_ERR, R8 ; error msg and new prompt
SYSCALL(puts, 1)
MOVE R7, R8
SYSCALL(puts, 1)
SYSCALL(crlf, 1)
RBRA INPUT_NP, 1
_INPUTN1EC RSUB ERR_CHECK, 1
RBRA INPUT_NP, 1 ; new prompt
; check for "exit" command
INPUT_N2 MOVE STR_IB_EXIT, R9
SYSCALL(strcmp, 1)
CMP R10, 0 ; exit command?
RBRA INPUT_N3, !Z ; no: try next
ADD R0, SP ; yes: restore stack and exit
RBRA END_PROGRAM, 1
; check for "cat" command
INPUT_N3 MOVE STR_IB_CAT, R9
SYSCALL(strcmp, 1)
CMP R10, 0 ; cat command?
RBRA INPUT_N4, !Z ; no: try next
MOVE 3, R5 ; lengh of command "CAT"
RSUB _INPUT_CHKPAR, 1 ; check for parameter
MOVE STRBUF, R8
ADD 4, R8 ; skip 4 chars ("cat ")
XOR R9, R9 ; CAT mode = ASCII
_DOCAT XOR R10, R10 ; default: no seek
XOR R11, R11
MOVE R8, R7 ; look for seek position
_DOCAT_SEARCHSP CMP @R7, 0 ; end of string?
RBRA _DOCAT_START, Z
CMP @R7++, ':' ; seek position separator?
RBRA _DOCAT_READSP, Z ; yes: read seek pos
RBRA _DOCAT_SEARCHSP, 1
_DOCAT_READSP MOVE R7, R10
RSUB _INPUT_READHEX, 1
MOVE 0, @--R7 ; delete : using zero term.
_DOCAT_START MOVE R8, R7 ; remember filename
RSUB CAT, 1 ; yes: perform the command
CMP R8, FAT32$ERR_FILENOTFOUND ; file not found?
RBRA _INPUTN3SE, !Z ; other error or no error
MOVE STR_IB_FNF_ERR, R8
SYSCALL(puts, 1)
MOVE R7, R8
SYSCALL(puts, 1)
SYSCALL(crlf, 1)
RBRA INPUT_NP, 1
_INPUTN3SE CMP R8, FAT23$ERR_SEEKTOOLARGE ; seek pos > file size?
RBRA _INPUTN3EC, !Z ; other error or no error
MOVE STR_IB_SEEK_ERR, R8
SYSCALL(puts, 1)
MOVE R11, R8
SYSCALL(puthex, 1)
MOVE R10, R8
SYSCALL(puthex, 1)
SYSCALL(crlf, 1)
RBRA INPUT_NP, 1
_INPUTN3EC RSUB ERR_CHECK, 1
RBRA INPUT_NP, 1 ; new prompt
; check for "cathex" command
INPUT_N4 MOVE STR_IB_CATHEX, R9
SYSCALL(strcmp, 1)
CMP R10, 0 ; cathex command?
RBRA INPUT_N5, !Z ; no: try next
MOVE 6, R5 ; lengh of command "CATHEX"
RSUB _INPUT_CHKPAR, 1 ; check for parameter
MOVE STRBUF, R8
ADD 7, R8 ; skip 7 chars ("cathex ")
MOVE 1, R9 ; CAT mode = HEX
RBRA _DOCAT, 1
; run testcase "read files in parallel"
INPUT_N5 MOVE STR_IB_TC_RFIP, R9
SYSCALL(strcmp, 1)
CMP R10, 0 ; testcase-rfip command?
RBRA INPUT_ERR, !Z ; no: error
RSUB TC_RFIP, 1 ; perform testcase
RBRA INPUT_NP, 1 ; new prompt
INPUT_ERR MOVE R8, R7
MOVE STR_REGCHK_ER, R8
SYSCALL(puts, 1)
MOVE R7, R8
SYSCALL(puts, 1)
SYSCALL(crlf, 1)
RBRA INPUT_NP, 1
INPUT_NP ADD R0, SP ; restore stack
RBRA NEW_PROMPT, 1 ; new prompt
; parameter check sub-sub routine
; R5 contains length of command
; R9 contains name of command
; on error: skip command and next prompt
_INPUT_CHKPAR MOVE STRBUF, R6
ADD R5, R6
CMP ' ', @R6
RBRA _INPUT_CHKERR, !Z
ADD 1, R6
CMP 0, @R6
RBRA _INPUT_CHKERR, Z
RET ; parameter OK: return
_INPUT_CHKERR MOVE STR_IB_NOPAR, R8
SYSCALL(puts, 1)
MOVE R9, R8
SYSCALL(puts, 1)
SYSCALL(crlf, 1)
SUB 1, SP ; avoid stack leak because ...
RBRA INPUT_NP, 1 ; ... we leave w/o RET
; hex read sub-sub routine
; INPUT: R10 = string to be converted
; OUTPUT: R11|R10 = 32bit HI|LO value
; on error: skip command and next prompt
_INPUT_READHEX INCRB
MOVE R10, R0 ; R0 = input string
XOR R10, R10 ; return value low word
XOR R11, R11 ; return value high word
MOVE R8, R6 ; save R8 & R9
MOVE R9, R7
MOVE R0, R8 ; all chars upper case
SYSCALL(str2upper, 1)
MOVE R0, R5 ; R5 = work pointer
MOVE 0, R1 ; 8 digits = 32bit
MOVE 12, R2 ; SHL counter
XOR R3, R3 ; temp LO word
XOR R4, R4 ; temp HI word
MOVE _INPUT_RHX_NIB, R9 ; R9 = hex nibbles
_INPUT_RHX_LP MOVE @R5++, R8
CMP R8, 0 ; zero term. too early?
RBRA _INPUT_RHX_ERR, Z ; yes: error
SYSCALL(strchr, 1) ; find hex niblle
CMP R10, 0
RBRA _INPUT_RHX_ERR, Z ; not found: error
SUB R9, R10 ; extract digit
SHL R2, R10 ; correct position for nibble
SUB 4, R2 ; next time: one nibble less
CMP R1, 3
RBRA _INPUT_RHX_HI, !N ; R1 <= 3: HI (walk form left)
ADD R10, R3 ; store to LO word
RBRA _INPUT_RHX_INC, 1
_INPUT_RHX_HI ADD R10, R4 ; store to HI word
_INPUT_RHX_INC ADD 1, R1 ; next nibble
CMP 8, R1 ; done?
RBRA _INPUT_RHX_DONE, Z ; yes
CMP 4, R1 ; reset SHL counter?
RBRA _INPUT_RHX_LP, !Z ; no: loop
MOVE 12, R2 ; yes: reset SHL counter ...
RBRA _INPUT_RHX_LP, 1 ; ... and loop
_INPUT_RHX_DONE MOVE R6, R8 ; restore R8 & R9
MOVE R7, R9
MOVE R3, R10 ; return value LO
MOVE R4, R11 ; return value HI
DECRB
RET
_INPUT_RHX_NIB .ASCII_W "0123456789ABCDEF"
_INPUT_RHX_ERR MOVE STR_IB_HEX_ERR, R8
SYSCALL(puts, 1)
MOVE R0, R8
SYSCALL(puts, 1)
SYSCALL(crlf, 1)
DECRB
SUB 1, SP ; avoid stack leak because ...
RBRA INPUT_NP, 1 ; ... we leave w/o RET
;=============================================================================
; Reads the files with the hardcoded file names "file1.txt", "file2.txt" and
; "file3.txt" in "parallel", i.e. 2 bytes at a time from each, 20 times in
; a row and displays the results.
; INPUT: <none>
; OUTPUT: <none>
;=============================================================================
TC_RFIP INCRB
XOR R0, R0
; open "file1.txt"
MOVE DEVICE_HANDLE, R8 ; R8 = device handle
ADD FAT32$FDH_STRUCT_SIZE, R0 ; clean-up counter
SUB FAT32$FDH_STRUCT_SIZE, SP ; reserve mem. on stack
MOVE SP, R9 ; R9 = file handle
MOVE STR_TC_RFIP_F1, R10 ; R10 = file name
MOVE R10, R12 ; remember name for error
XOR R11, R11 ; R11 = 0 => / sep.char
RSUB FAT32$FILE_OPEN, 1 ; open file
CMP R10, 0 ; file not found?
RBRA _TC_RFIP_ERRO, !Z ; yes: print error and end
MOVE R9, R1 ; R1 = file handle 1
; open "file2.txt"
ADD FAT32$FDH_STRUCT_SIZE, R0
SUB FAT32$FDH_STRUCT_SIZE, SP
MOVE SP, R9
MOVE STR_TC_RFIP_F2, R10
MOVE R10, R12
RSUB FAT32$FILE_OPEN, 1
CMP R10, 0
RBRA _TC_RFIP_ERRO, !Z
MOVE R9, R2 ; R2 = file handle 2
; open "file3.txt"
ADD FAT32$FDH_STRUCT_SIZE, R0
SUB FAT32$FDH_STRUCT_SIZE, SP
MOVE SP, R9
MOVE STR_TC_RFIP_F3, R10
MOVE R10, R12
RSUB FAT32$FILE_OPEN, 1
CMP R10, 0
RBRA _TC_RFIP_ERRO, !Z
MOVE R9, R3 ; R3 = file handle 3
MOVE 20, R4 ; perform 20 iterations
_TC_RFIP_LOOP MOVE STR_TC_RFIP_LH1, R8
SYSCALL(puts, 1)
MOVE R1, R8 ; file 1
RSUB FAT32$FILE_RB, 1 ; read one byte
CMP R10, 0
RBRA _TC_RFIP_ERRF, !Z
MOVE R9, R8
SYSCALL(putc, 1)
MOVE R1, R8
RSUB FAT32$FILE_RB, 1 ; read one byte
CMP R10, 0
RBRA _TC_RFIP_ERRF, !Z
MOVE R9, R8
SYSCALL(putc, 1)
MOVE STR_SPACE2, R8
SYSCALL(puts, 1)
MOVE STR_TC_RFIP_LH2, R8
SYSCALL(puts, 1)
MOVE R2, R8 ; file 2
RSUB FAT32$FILE_RB, 1 ; read one byte
CMP R10, 0
RBRA _TC_RFIP_ERRF, !Z
MOVE R9, R8
SYSCALL(putc, 1)
MOVE R2, R8
RSUB FAT32$FILE_RB, 1 ; read one byte
CMP R10, 0
RBRA _TC_RFIP_ERRF, !Z
MOVE R9, R8
SYSCALL(putc, 1)
MOVE STR_SPACE2, R8
SYSCALL(puts, 1)
MOVE STR_TC_RFIP_LH3, R8
SYSCALL(puts, 1)
MOVE R3, R8 ; file 2
RSUB FAT32$FILE_RB, 1 ; read one byte
CMP R10, 0
RBRA _TC_RFIP_ERRF, !Z
MOVE R9, R8
SYSCALL(putc, 1)
MOVE R3, R8
RSUB FAT32$FILE_RB, 1 ; read one byte
CMP R10, 0
RBRA _TC_RFIP_ERRF, !Z
MOVE R9, R8
SYSCALL(putc, 1)
MOVE STR_SPACE2, R8
SYSCALL(puts, 1)
SYSCALL(crlf, 1)
SUB 1, R4
RBRA _TC_RFIP_LOOP, !Z
_TC_RFIP_END ADD R0, SP ; restore stack
DECRB
RET
_TC_RFIP_ERRO MOVE STR_IB_FNF_ERR, R8
SYSCALL(puts, 1);
MOVE R12, R8
SYSCALL(puts, 1);
SYSCALL(crlf, 1)
RBRA _TC_RFIP_END, 1
_TC_RFIP_ERRF MOVE STR_TC_RFIP_EF, R8
SYSCALL(puts, 1)
RBRA _TC_RFIP_END, 1
;=============================================================================
; Lists the contents of a file
; INPUT: R8 = filename string
; R9 = mode: 0 = ASCII, 1 = HEX
; R10 = LO seek position
; R11 = HI seek position
; OUTPUT: R8 is 0, if everything is OK, otherwise R8 = error code
;=============================================================================
CAT INCRB
MOVE R10, R0 ; R1|R0 = HI|LO seek pos
MOVE R11, R1
MOVE R9, R5 ; R5 = mode selector
XOR R4, R4 ; R4 = count hex per line
MOVE R8, R10 ; R10 = filename string
MOVE DEVICE_HANDLE, R8 ; R8 = device handle
SUB FAT32$FDH_STRUCT_SIZE, SP
MOVE SP, R9 ; R9 = file handle
XOR R11, R11 ; R11 = 0 => / sep.char
RSUB FAT32$FILE_OPEN, 1
MOVE R10, R8 ; return error code
RBRA _CAT_RET, !Z ; return if error
MOVE R9, R8 ; R8 = file handle
XOR R6, R6 ; R6 = CR/LF detector
; perform seek
; (the seek function is smart enough not to seek, if 0 is
; is passed as the seek position)
MOVE R9, @--SP ; save R9 & R10
MOVE R10, @--SP
MOVE R0, R9 ; R10|R9 = seek pos
MOVE R1, R10
RSUB FAT32$FILE_SEEK, 1 ; perform seek
CMP R9, 0 ; error?
RBRA _CAT_SEEKOK, Z ; no
MOVE R9, R8 ; yes: exit with error
MOVE @SP++, R10 ; restore R9 & 10
MOVE @SP++, R9
RBRA _CAT_RET, 1 ; exit with error
_CAT_SEEKOK MOVE @SP++, R10 ; restore R9 & R10
MOVE @SP++, R9
; simple loop to print everything until EOF is reached
_CAT_LOOP RSUB FAT32$FILE_RB, 1 ; read byte
CMP R10, FAT32$EOF ; EOF reached?
RBRA _CAT_EOF, Z ; yes: print crlf and exit
CMP R10, 0 ; another error?
RBRA _CAT_PRINT, Z ; no: go on
MOVE R10, R8 ; yes: return error
RBRA _CAT_RET, 1
_CAT_PRINT CMP R5, 0 ; print ASCII?
RBRA _CAT_PRINT_A, Z ; yes
CMP R4, 16 ; no: check hex counter
RBRA _CAT_PRINT_H, !Z ; print hex word
SYSCALL(crlf, 1) ; new line
XOR R4, R4 ; reset hex counter
_CAT_PRINT_H MOVE R8, R7
MOVE R9, R8
SYSCALL(puthex, 1) ; print hex word
MOVE STR_SPACE1, R8
SYSCALL(puts, 1)
ADD 1, R4 ; increase hex counter
MOVE R7, R8
RBRA _CAT_LOOP, 1
; support LF only CR only and CR/LF line ends
_CAT_PRINT_A CMP R9, CHR$CR ; CR?
RBRA _CAT_PT_CR, Z ; yes
CMP R9, CHR$LF ; LF?
RBRA _CAT_PT_LF, Z ; yes
MOVE R8, R7 ; no: print character
MOVE R9, R8
SYSCALL(putc, 1)
MOVE R7, R8
RBRA _CAT_LOOP, 1 ; next byte
_CAT_PT_CR SYSCALL(crlf, 1) ; next line
MOVE 1, R6 ; CR/LF detector ON
RBRA _CAT_LOOP, 1
_CAT_PT_LF CMP 1, R6 ; CR/LF?
RBRA _CAT_PT_LFI, Z ; yes: ignore LF
SYSCALL(crlf, 1) ; no: do the LF
RBRA _CAT_LOOP, 1
_CAT_PT_LFI XOR R6, R6 ; CR/LF OFF
RBRA _CAT_LOOP, 1
_CAT_EOF SYSCALL(crlf, 1)
MOVE 0, R8
_CAT_RET ADD FAT32$FDH_STRUCT_SIZE, SP ; restore stack
MOVE R5, R9 ; restore R9
MOVE R0, R10 ; restore R10 & R11
MOVE R1, R11
DECRB
RET
;=============================================================================
; List current directory and use the settings stored in
; DIR_FLAGS and PRINT_FLAGS
;
; WARNING: In case of a fatal exit via ERR_CHECK, this leads to stack leaks
; (This does not matter here, because this function is not part of the
; reusable functions. It is just one of the test case functions that is
; testing the resubale functions. The reusable functions themselves are of
; course cleaning up the stack, also on error.)
;=============================================================================
DIR INCRB
MOVE R8, R0
MOVE R9, R1
MOVE R10, R2
INCRB
XOR R4, R4 ; file counter = 0
MOVE DEVICE_HANDLE, R8 ; open directory for browsing
SUB FAT32$FDH_STRUCT_SIZE, SP
MOVE SP, R0 ; R0 = directory handle
MOVE R0, R9
RSUB FAT32$DIR_OPEN, 1
MOVE R8, R0 ; test: R8 should still be R0
MOVE R9, R8
RSUB ERR_CHECK, 1
SUB FAT32$DE_STRUCT_SIZE, SP
MOVE SP, R1 ; R1 = directory entry struct
NEXT_DIR_ENT MOVE R0, R8
MOVE R1, R9
MOVE DIR_FLAGS, R10
MOVE @R10, R10
RSUB FAT32$DIR_LIST, 1
MOVE R11, R8
RSUB ERR_CHECK, 1
CMP R10, 1 ; current entry valid?
RBRA DIR_DONE, !Z ; no: end
MOVE R1, R8 ; yes: print entry
MOVE PRINT_FLAGS, R9
MOVE @R9, R9
RSUB FAT32$PRINT_DE, 1
ADD 1, R4 ; increase file counter
; also includes "." and ".."
RBRA NEXT_DIR_ENT, 1 ; next entry
DIR_DONE XOR R9, R9 ; print file counter (decimal)
MOVE R4, R8
SUB 11, SP
MOVE SP, R10
RSUB H2D_ASCII, 1
MOVE R11, R8
SYSCALL(puts, 1)
MOVE STR_IB_DIR_SUM, R8
SYSCALL(puts, 1)
SYSCALL(crlf, 1)
ADD 11, SP
; restore stack
; (does not happen in case of fatal error, i.e. in such a
; a case we have a stack leak and return to the monitor)
ADD FAT32$FDH_STRUCT_SIZE, SP
ADD FAT32$DE_STRUCT_SIZE, SP
DECRB
MOVE R0, R8
MOVE R1, R9
MOVE R2, R10
DECRB
RET
;=============================================================================
; Interactive dump of linear SD Card addresses
;=============================================================================
; allow interactive dumps of arbitrary addresses
IA_DUMP MOVE STR_IA_TITLE, R8
SYSCALL(puts, 1)
MOVE STR_IA_HIGH, R8
SYSCALL(puts, 1)
SYSCALL(gethex, 1)
MOVE R8, R1 ; block addr hi word
SYSCALL(crlf, 1)
MOVE STR_IA_LOW, R8
SYSCALL(puts, 1)
SYSCALL(gethex, 1)
MOVE R8, R0 ; block read addr lo word
SYSCALL(crlf, 1)
SYSCALL(crlf, 1)
; read 512 bytes from given address
; address must be a multiple of 512, otherwise the system
; will automatically "round it down" to the next 512 block
MOVE R0, R8
MOVE R1, R9
RSUB SD$READ_BLOCK, 1
RSUB ERR_CHECK, 1
; output the 512 bytes of the buffer
MOVE 0, R6
OUTPUT_LOOP MOVE R6, R8 ; read byte at position R6...
RSUB SD$READ_BYTE, 1 ; ...from buffer
SYSCALL(puthex, 1) ; output hex value
MOVE STR_SPACE2, R8 ; output two separating spaces
SYSCALL(puts, 1)
ADD 1, R6 ; next byte
MOVE R6, R8 ; if bytecount mod 16 is zero,
MOVE 16, R9 ; i.e. if a line has 16 hex
SYSCALL(divu, 1) ; numbers, then output a
CMP 0, R11 ; CR/LF so that the output
RBRA OUTPUT_LOOP_1, !Z ; is nicely formatted
SYSCALL(crlf, 1)
OUTPUT_LOOP_1 CMP 512, R6
RBRA OUTPUT_LOOP, !Z
SYSCALL(crlf, 1)
; check if the user like to dump another buffer
MOVE STR_IA_AGAIN, R8
SYSCALL(puts, 1)
SYSCALL(getc, 1)
SYSCALL(crlf, 1)
CMP 'y', R8
RBRA IA_DUMP, Z
RBRA MAIN_MENU, 1
; back to monitor
END_PROGRAM SYSCALL(exit, 1)
;=============================================================================
; Helper sub routines for performing the checks
;=============================================================================
; Register check subroutine: Expects the to be written and read-back
; register in R8, the value in R9 and the name string in R10
; R11: if 1 then no write is performed, only the read-back. This is needed
; for the CSR register as writing to it performs an action. It is also
; advisable for the ERROR register as you cannot write to it
; R11: if 2, similar to 1 but mask bits 13 and 12
REG_CHECK INCRB
MOVE R8, R0
; print "checking <register name>"
MOVE STR_REGCHK_R, R8
SYSCALL(puts, 1)
MOVE R10, R8
SYSCALL(puts, 1)
CMP R11, 1
RBRA _REG_CHECK_DW, Z
CMP R11, 2
RBRA _REG_CHECK_DW, Z
; write SD card register, read it back and test the value
MOVE R9, @R0 ; write to the register
_REG_CHECK_DW MOVE @R0, R1 ; read it back
CMP R11, 2
RBRA _REG_CHECK_DC, !Z
AND 0xCFFF, R1 ; mask bits 13 and 12
_REG_CHECK_DC CMP R1, R9 ; check if the read val is ok
RBRA _REG_CHECK_OK, Z ; jump if OK
MOVE STR_FAILED, R8 ; print FAILED, if not OK...
SYSCALL(puts, 1)
MOVE R1, R8
SYSCALL(puthex, 1) ; ...and show the wrong value
RBRA _REG_CHECK_CNT, 1
_REG_CHECK_OK MOVE STR_OK, R8 ; print OK, if OK
SYSCALL(puts, 1)
_REG_CHECK_CNT SYSCALL(crlf, 1)
DECRB
RET
; Error check subroutine: If R8 is nonzero, then the error code is printed
; and then the program is terminated
ERR_CHECK INCRB
CMP R8, 0 ; if no error: return
RBRA _ERR_CHECK_END, Z
MOVE R8, R9 ; save error code
MOVE STR_ERR_END, R8 ; print error string
SYSCALL(puts, 1)
MOVE R9, R8 ; print error code
SYSCALL(puthex, 1)
SYSCALL(crlf, 1)
DECRB ; terminate execution
SYSCALL(exit, 1)
_ERR_CHECK_END DECRB
RET
; Output a title and a DWORD value from the device handle
; R8 = Title
; R9 = offset lo
; R10 = offset hi
; R11: output DWORD, when R11 = 1, else output WORD
OUTPUT_DW INCRB
MOVE R8, R0
SYSCALL(puts, 1)
MOVE DEVICE_HANDLE, R8
ADD R9, R8
MOVE @R8, R8
SYSCALL(puthex, 1)
CMP 1, R11
RBRA _OUTPUT_DW_END, !Z
MOVE DEVICE_HANDLE, R8
ADD R10, R8
MOVE @R8, R8
SYSCALL(puthex, 1)
_OUTPUT_DW_END SYSCALL(crlf, 1)
MOVE R0, R8
DECRB
RET
;=============================================================================
; Variables
;=============================================================================
DEVICE_HANDLE .BLOCK FAT32$DEV_STRUCT_SIZE ; mount struct / device handle
DIR_FLAGS .BLOCK 1 ; which file to browse?
PRINT_FLAGS .BLOCK 1 ; which attributes to print?
;=============================================================================
; String constants
;=============================================================================
STR_TITLE .ASCII_P "SD Card development testbed - done by sy2002 in June .. August 2016\n"
.ASCII_W "=====================================================================\n\n"
STR_OK .ASCII_W "OK"
STR_FAILED .ASCII_W "FAILED: "
STR_SPACE1 .ASCII_W " "
STR_SPACE2 .ASCII_W " "
STR_RESET .ASCII_W "Resetting SD Card: "
STR_CARDTYPE .ASCII_W "Detected card type: "
STR_CT_NA .ASCII_W "FAILED\n"
STR_CT_SDV1 .ASCII_W "SD V1\n"
STR_CT_SDV2 .ASCII_W "SD V2\n"
STR_CT_SDHC .ASCII_W "SDHC\n"
STR_CT_INCONS .ASCII_W "<illegal return value / inconsistency asm/vhdl>\n"
STR_REGCHK_T .ASCII_W "Register write and read-back:\n"
STR_REGCHK_R .ASCII_W " checking "
STR_REGCHK_AL .ASCII_W "ADDR_LO: "
STR_REGCHK_AH .ASCII_W "ADDR_HI: "
STR_REGCHK_DP .ASCII_W "DATA_POS: "
STR_REGCHK_DTA .ASCII_W "DATA: "
STR_REGCHK_ER .ASCII_W "ERROR: "
STR_REGCHK_CS .ASCII_W "CSR: "
STR_MEN_TITLE .ASCII_W "\nChoose a test case to proceed:\n"
STR_MEN_1 .ASCII_P " 1. Dump raw data from SD Card\n"
.ASCII_P " 2. Mount partition 1 (assuming valid MBR and a FAT32 file system)\n"
.ASCII_W " 3. Exit\n\n"
STR_IA_TITLE .ASCII_W "Read 512 byte block from SD card and output it:\n"
STR_IA_HIGH .ASCII_W " Address HIGH word: "
STR_IA_LOW .ASCII_W " Address LOW word: "
STR_IA_AGAIN .ASCII_W "Enter 'y' for reading another block: "
STR_ERR_END .ASCII_W "\nTERMINATED DUE TO FATAL ERROR: "
STR_MNT_TILE .ASCII_W "Mounting partition #1 of SD Card as FAT32: "
STR_MNT_FSSTART .ASCII_W " file system start address (LBA): "
STR_MNT_FATSTRT .ASCII_W " FAT start address (LBA): "
STR_MNT_CLSSTRT .ASCII_W " clusters start address (LBA): "
STR_MNT_SPC .ASCII_W " sectors per cluster: "
STR_MNT_ROOT .ASCII_W " root directory first cluster (LBA): "
STR_MNT_ACTIVE .ASCII_W " active directory first cluster (LBA): "
STR_INTERACTIVE .ASCII_P "Interactive browing mode:\n"
.ASCII_P " navigate: dir and cd <path> (use / as a path separator)\n"
.ASCII_P " list files (complex names are allowed, use / as path separator):\n"
.ASCII_P " cat <filename> and cathex <filename>\n"
.ASCII_P " for seeking within the file, add a 32bit hex number (8 digits)\n"
.ASCII_P " separated by a : after the filename, e.g. <filename>:00000000\n"
.ASCII_P " run read-files-in-parallel testcase: testcase-rfip\n"
.ASCII_W " end interactive browsing mode using exit\n"
STR_PROMPT .ASCII_W "SDCARD> "
STR_IB_DIR .ASCII_W "DIR"
STR_IB_DIR_SUM .ASCII_W " files and directories found."
STR_IB_CD .ASCII_W "CD"
STR_IB_CD_ERR .ASCII_W "Path not found: "
STR_IB_EXIT .ASCII_W "EXIT"
STR_IB_CAT .ASCII_W "CAT"
STR_IB_CATHEX .ASCII_W "CATHEX"
STR_IB_TC_RFIP .ASCII_W "TESTCASE-RFIP"
STR_IB_FNF_ERR .ASCII_W "File not found: "
STR_IB_NOPAR .ASCII_W "Parameter missing for command: "
STR_IB_HEX_ERR .ASCII_W "Illegal 32bit hex number for seek position: "
STR_IB_SEEK_ERR .ASCII_W "Seek position larger than file size: "
STR_TC_RFIP_LH1 .ASCII_W "1:"
STR_TC_RFIP_LH2 .ASCII_W "2:"
STR_TC_RFIP_LH3 .ASCII_W "3:"
STR_TC_RFIP_F1 .ASCII_W "file1.txt"
STR_TC_RFIP_F2 .ASCII_W "file2.txt"
STR_TC_RFIP_F3 .ASCII_W "file3.txt"
STR_TC_RFIP_EF .ASCII_W "Error reading one of the files. Terminated.\n"
;=============================================================================
;=============================================================================
;
; REUSABLE FAT32 CONSTANT DEFINITIONS AND FUNCTIONS START HERE
;
;=============================================================================
;=============================================================================
; INTERNAL CONSTANTS FOR PARSING MBR and FAT32
; (not meant to be published in sysdef.asm)
FAT32$MBR_LO .EQU 0x0000 ; low byte of MBRs position (linear addressing)
FAT32$MBR_HI .EQU 0x0000 ; high byte of MBRs position (linear addressing)
FAT32$MBR_MAGIC .EQU 0xAA55 ; magic word at address #510 (decoded to big endian, stored as 0x55AA)
FAT32$MBR_MAGIC_ADDR .EQU 0x01FE ; absolute address of magic bytes
FAT32$MBR_PARTTBL_START .EQU 0x01BE ; absolute start address of the partition table
FAT32$MBR_PARTTBL_RSIZE .EQU 0x0010 ; size of each partition table record in bytes
FAT32$MBR_PT_TYPE .EQU 0x0004 ; partition type address (relative to the partition table)
FAT32$MBR_PT_TYPE_C1 .EQU 0x000B ; type flag (alternative 1) for FAT32
FAT32$MBR_PT_TYPE_C2 .EQU 0x000C ; type flag (alternative 2) for FAT32
FAT32$MBR_PT_FS_START .EQU 0x0008 ; file system start sector (relative to the partition table)
FAT32$MAGIC .EQU 0xAA55 ; volume id magic
FAT32$MAGIC_OFS .EQU 0x01FE ; offset of volume id magic (word)
FAT32$JMP1_1 .EQU 0x00EB ; Sanity check: Jump instruction to boot code:
FAT32$JMP2 .EQU 0x00E9 ; (JMP1_1 AND JMP1_2) OR JMP_2
FAT32$JMP1_OR_2_OFS .EQU 0x0000
FAT32$JMP1_2 .EQU 0x0090
FAT32$JMP1_2_OFS .EQU 0x0002
FAT32$SANITY .EQU 0x0000 ; (word) must be zero on FAT32
FAT32$SANITY_OFS .EQU 0x0016
FAT32$SECTOR_SIZE .EQU 0x0200 ; we assume the ubiquitous 512-byte sectors
FAT32$SECTOR_SIZE_OFS .EQU 0x000B ; offset of sector size in volume id (word)
FAT32$FATNUM .EQU 0x0002 ; number of FATs (needs to be always two) (byte)
FAT32$FATNUM_OFS .EQU 0x0010 ; offset of number of FATs in volume id (byte)
FAT32$SECPERCLUS_OFS .EQU 0x000D ; should be 1, 2, 4, 8, 16, 32, 64, 128 (byte)
FAT32$RSSECCNT_OFS .EQU 0x000E ; number of reserved sectors (word)
FAT32$SECPERFAT_OFS .EQU 0x0024 ; sectors per fat, depends on disk size (dword)
FAT32$ROOTCLUS_OFS .EQU 0x002C ; root directory first cluster (dword)
FAT32$FE_SIZE .EQU 0x0020 ; size of one directory entry
FAT32$FE_DEL .EQU 0x00E5 ; flag for deleted (or "empty") entry
FAT32$FE_NOMORE .EQU 0x0000 ; flag for last deleted (or "empty") entry, no more are following
FAT32$FE_ATTRIB .EQU 0x000B ; offset of the file attribute
FAT32$FE_FILESIZE .EQU 0x001C ; offset of the file size (dword)
FAT32$FE_FILEDATE .EQU 0x0018 ; date of last write (creation is treated as write)
FAT32$FE_FILETIME .EQU 0x0016 ; time of last write (creation is treated as write)
FAT32$FE_CLUS_LO .EQU 0x001A ; first cluster: low word
FAT32$FE_CLUS_HI .EQU 0x0014 ; first cluster: high word
FAT32$FE_DISPLAYCASE .EQU 0x000C ; only for short names: bit 3 = display filename lower case, bit 4 = ext. lower case
FAT32$FE_DC_NAME_MASK .EQU 0x0008 ; FAT32$FE_DISPLAYCASE: filter for bit 3
FAT32$FE_DC_EXT_MASK .EQU 0x0010 ; FAT32$FE_DISPLAYCASE: filter for bit 4
FAT32$FE_SPECIAL_CHAR .EQU 0x0005 ; if the first char is this, then replace by E5
FAT32$FE_PADDING .EQU 0x0020 ; padding used in short file names
FAT32$FE_LE_FINAL .EQU 0x0040 ; flag signalling last entry of a long filename table
FAT32$FE_LE_CHKSUM .EQU 0x000D ; long filename checksum
FAT32$FE_LE_C1_5 .EQU 0x0001 ; long filename: characters 1 .. 5
FAT32$FE_LE_C6_11 .EQU 0x000E ; long filename: characters 6 .. 11
FAT32$FE_LE_C12_13 .EQU 0x001C ; long filename: characters 12 .. 13
FAT32$INT_LONG_NAME .EQU 0x000F ; internal flag used to filter for long file names
FAT32$INT_LONG_MASK .EQU 0x003F ; internal mask for filtering long file file and directory names
FAT32$PRINT_DE_DIR_Y .ASCII_W "<DIR> "
FAT32$PRINT_DE_DIR_N .ASCII_W " "
FAT32$PRINT_DE_DIR_S .ASCII_W " "
FAT32$PRINT_DE_AN .ASCII_W " "
FAT32$PRINT_DE_AH .ASCII_W "H"
FAT32$PRINT_DE_AR .ASCII_W "R"
FAT32$PRINT_DE_AS .ASCII_W "S"
FAT32$PRINT_DE_AA .ASCII_W "A"
FAT32$PRINT_DE_DATE .ASCII_W "-"
FAT32$PRINT_DE_TIME .ASCII_W ":"
;
;*****************************************************************************
;* SD$RESET resets the SD Card
;*
;* R8: 0, if everything went OK, otherwise the error code
;*****************************************************************************
;
SD$RESET INCRB
MOVE IO$SD_CSR, R0
MOVE SD$CMD_RESET, @R0
RSUB SD$WAIT_BUSY, 1
DECRB
RET
;
;*****************************************************************************
;* SD$READ_BLOCK reads a 512 byte block from the SD Card
;*
;* INPUT: R8/R9 = LO/HI words of the 32-bit block address
;* OUTPUT: R8 = 0 (no error), or error code
;*
;* The read data is stored inside 512 byte buffer of the the SD controller
;* memory that can then be accessed via SD$READ_BYTE.
;*
;* IMPORTANT: 512-byte block addressing is used always, i.e. independent of
;* the SD Card type. Address #0 means 0..511, address #1 means 512..1023, ..
;*****************************************************************************
;
SD$READ_BLOCK INCRB
MOVE R8, R1 ; save R8 due to WAIT_BUSY
RSUB SD$WAIT_BUSY, 1 ; wait to be ready
CMP R8, 0 ; error?
RBRA _SD$RB_END, !Z ; yes: return
MOVE IO$SD_ADDR_LO, R0 ; lo word of 32-bit address
MOVE R1, @R0
MOVE IO$SD_ADDR_HI, R0 ; hi word of 32-bit address
MOVE R9, @R0
MOVE IO$SD_CSR, R0
MOVE SD$CMD_READ, @R0 ; issue block read command
RSUB SD$WAIT_BUSY, 1 ; wait until finished
_SD$RB_END DECRB
RET
;
;*****************************************************************************
;* SD$WRITE_BLOCK writes a 512 byte block to the SD Card
;*
;* @TODO: Implement and document
;*****************************************************************************
;
SD$WRITE_BLOCK INCRB
DECRB
RET
;
;*****************************************************************************
;* SD$READ_BYTE reads a byte from the read buffer memory of the controller
;*
;* INPUT: R8 = address between 0 .. 511
;* OUTPUT: R8 = byte
;*
;* No boundary checks are performed.
;*****************************************************************************
;
SD$READ_BYTE INCRB
MOVE IO$SD_DATA_POS, R0
MOVE R8, @R0
MOVE IO$SD_DATA, R0
MOVE @R0, R8
DECRB
RET
;
;*****************************************************************************
;* SD$WRITE_BYTE writes a byte to the write memory buffer of the controller
;*
;* @TODO: Implement and document
;*****************************************************************************
;
SD$WRITE_BYTE INCRB
DECRB
RET
;
;*****************************************************************************
;* SD$WAIT_BUSY waits, while the SD Card is executing any command
;*
;* R8: 0, if everything went OK, otherwise the error code
;*
;* Side effect: Starts the cycle counter (if it was stopped), but does not
;* reset the value, so that other countings are not influenced.
;*****************************************************************************
;
SD$WAIT_BUSY INCRB
; Make sure that the cycle counter is running for being
; able to measure the timeout. Do not reset it, but find
; the target value via addition (wrap around is OK), so that
; other running cycle counting processes are not disturbed
; by this
MOVE IO$CYC_STATE, R0 ; make sure, the cycle counter
OR CYC$RUN, @R0 ; is running
MOVE IO$CYC_MID, R3
MOVE @R3, R7
ADD SD$TIMEOUT_MID, R7
; check busy status of SD card and timeout
MOVE IO$SD_CSR, R0 ; SD Card Command & Status
MOVE IO$SD_ERROR, R2 ; SD Card Errors
_SD$WAIT_BUSY_L MOVE @R3, R1 ; check for timeout
CMP R1, R7 ; timeout reached
RBRA _SD$WAIT_TO, Z ; yes: return timeout
MOVE @R0, R1 ; read CSR register
AND SD$BIT_BUSY, R1 ; check busy flag
RBRA _SD$WAIT_BUSY_L, !Z ; loop if busy flag is set
XOR R8, R8 ; assume no error, but did an
MOVE @R0, R1 ; read CSR register
AND SD$BIT_ERROR, R1 ; error flag?
RBRA _SD$WAIT_END, Z ; no error
_SD$WAIT_ERR MOVE @R2, R8 ; return error value
RBRA _SD$WAIT_END, 1
_SD$WAIT_TO MOVE SD$ERR_TIMEOUT, R8
_SD$WAIT_END DECRB
RET
;
;*****************************************************************************
;* FAT32$MOUNT_SD mounts a SD card partition as a FAT32 file system
;*
;* Wrapper to simplify the use of the generic FAT32$MOUNT function. Read the
;* documentation of FAT32$MOUNT to learn more.
;*
;* INPUT: R8 points to a 17 word large empty structure. This structure will
;* be filled by the mount function and it therefore becomes the device
;* handle that you need for subsequent FAT32 function calls. For being
;* on the safe side: Instead of hardcoding "17", use the constant
;* FAT32$DEV_STRUCT_SIZE instead.
;* R9 partition number to mount (1 .. 4)
;* OUTPUT: R8 points to the handle (identical to the input value of R8)
;* R9 contains 0 if OK, otherwise the error code
;*****************************************************************************
;
FAT32$MOUNT_SD INCRB
; store the function pointers to the SD card specific
; device handling functions within the mount structure
; and set the partition to 1
MOVE R8, R0
ADD FAT32$DEV_RESET, R0
MOVE SD$RESET, @R0
MOVE R8, R0
ADD FAT32$DEV_BLOCK_READ, R0
MOVE SD$READ_BLOCK, @R0
MOVE R8, R0
ADD FAT32$DEV_BLOCK_WRITE, R0
MOVE SD$WRITE_BLOCK, @R0
MOVE R8, R0
ADD FAT32$DEV_BYTE_READ, R0
MOVE SD$READ_BYTE, @R0
MOVE R8, R0
ADD FAT32$DEV_BYTE_WRITE, R0
MOVE SD$WRITE_BYTE, @R0
MOVE R8, R0
ADD FAT32$DEV_PARTITION, R0
MOVE R9, @R0
; call master mount function
RSUB FAT32$MOUNT, 1
DECRB
RET
;
;*****************************************************************************
;* FAT32$MOUNT mounts a FAT32 file system on arbitrary hardware
;*
;* The abstraction requires 5 functions to be implemented: Read and write
;* a 512-byte-sized sector using LBA mode. Read and write a byte from within
;* a buffer that contains the current sector. Reset the device. The function
;* signatures and behaviour needs to be equivalent to the SD card functions
;* that are part of this library. You need to pass pointers to these functions
;* to the mount function call in the mount initialization structure.
;*
;* All subsequent calls to FAT32 functions expect as the first parameter a
;* pointer to the mount data structure (aka device handle) that is being
;* generated during the execution of this function. With this mechanism, an
;* arbitrary amount of filesystems can be mounted on an arbitrary amount and
;* type of hardware.
;*
;* INPUT: R8: pointer to the mount initialization structure that is build up
;* in the following form. Important: The structure is 17 words large, that
;* means that a call to FAT32$MOUNT will append more words to the structure
;* than the ones, that have to be pre-filled before calling FAT32$MOUNT:
;* word #0: pointer to a device reset function, similar to SD$RESET
;* word #1: pointer to a block read function, similar to SD$READ_BLOCK
;* word #2: pointer to a block write function, similar to SD$WRITE_BLOCK
;* word #3: pointer to a byte read function, similar to SD$READ_BYTE
;* word #4: pointer to a byte write function, similar to SD$WRITE_BYTE
;* word #5: number of the partition to be mounted (0x0001 .. 0x0004)
;* word #6 .. word #17 : will be filled by by FAT32$MOUNT, their layout is
;* as described in the FAT32$DEV_* constants beginning
;* from index #7 on.
;*
;* For being on the safe side: Instead of hardcoding "17" as the size of the
;* whole mount data structure (device handle) use the constant
;* FAT32$DEV_STRUCT_SIZE instead.
;*
;* OUTPUT: R8 is preserved and still points to the structure that has been
;* filled by the function from word #6 on.
;* R9 contains 0, if everything went OK, otherwise it contains the error code
;*****************************************************************************
;
FAT32$MOUNT INCRB ; sometimes more registers ..
INCRB ; .. are needed, so 2x INCRB
MOVE R8, R0 ; save pointer to structure
MOVE R8, @--SP
MOVE R10, @--SP
MOVE R11, @--SP
MOVE R12, @--SP
; reset the device and exit on error
_F32_MNT_RESET MOVE FAT32$DEV_RESET, R10
MOVE R0, R11
RSUB FAT32$CALL_DEV, 1
MOVE R8, R9
RBRA _F32_MNT_END, !Z
; read the Master Boot record and sector and check the
; magic bytes as a first indication of a working MBR
MOVE FAT32$MBR_LO, R8 ; address (LBA) of MBR
MOVE FAT32$MBR_HI, R9
MOVE FAT32$DEV_BLOCK_READ, R10 ; read 512 byte sector
MOVE R0, R11
RSUB FAT32$CALL_DEV, 1
CMP 0, R8
RBRA _F32_MNT_MBR, Z
MOVE R8, R9
RBRA _F32_MNT_END, 1
_F32_MNT_MBR MOVE R0, R8
MOVE FAT32$MBR_MAGIC_ADDR, R9 ; read magic word
RSUB FAT32$READ_W, 1
CMP FAT32$MBR_MAGIC, R10 ; compare magic word
RBRA _F32_MNT_PNCHK, Z ; magic correct: go on
MOVE FAT32$ERR_MBR, R9 ; magic wrong: error code
RBRA _F32_MNT_END, 1
; calculate partition table start offset: this is defined as:
; FAT32$MBR_PARTTBL_START +
; ((#partition - 1) x FAT32$MBR_PARTTBL_RSIZE)
; we need to subtract 1 from #partition, because the number
; of partitions are defined to be between 1 and 4
_F32_MNT_PNCHK MOVE R0, R8 ; get device handle
ADD FAT32$DEV_PARTITION, R8 ; retrieve the partition..
MOVE @R8, R8 ; ..that shall be mounted
CMP R8, 0 ; partition must be 1 .. 4
RBRA _F32_MNT_PNGOK, N ; partition is > 0: OK
_F32_MNT_PNERR MOVE FAT32$ERR_PARTITION_NO, R9 ; exit with an error
RBRA _F32_MNT_END, 1
_F32_MNT_PNGOK CMP R8, 4
RBRA _F32_MNT_POFFS, !N ; partition is <= 4: OK
RBRA _F32_MNT_PNERR, 1 ; else exit with an error
_F32_MNT_POFFS SUB 1, R8 ; #partition - 1
MOVE FAT32$MBR_PARTTBL_RSIZE, R9 ; mult. with record size
SYSCALL(mulu, 1) ; result is in R10
ADD FAT32$MBR_PARTTBL_START, R10
MOVE R10, R12 ; offset is now in R12
; read and decode the selected partition table entry:
; 1. check for the FAT32 type description
; 2. check, if the linear start address of the file system
; of the first partition is less than ~31MB, because
; otherwise we would need better multiplication functions
; (see explanation about hardcoded partition 1 above)
; 3. calculate the linar start address by multiplying the
; sector number with 512
_F32_MNT_RPAR MOVE R12, R8 ; partition table offset
ADD FAT32$MBR_PT_TYPE, R8 ; type flag: FAT32 ?
MOVE FAT32$DEV_BYTE_READ, R10
MOVE R0, R11
RSUB FAT32$CALL_DEV, 1
CMP FAT32$MBR_PT_TYPE_C1, R8 ; check for alternative 1
RBRA _F32_MNT_TCOK, Z ; OK: continue
CMP FAT32$MBR_PT_TYPE_C2, R8 ; check for alternative 2
RBRA _F32_MNT_TCOK, Z ; OK: continue
MOVE FAT32$ERR_PARTTBL, R9 ; not OK: error code
RBRA _F32_MNT_END, 1
_F32_MNT_TCOK MOVE R0, R8 ; device handle
MOVE R12, R9 ; partition table offset
ADD FAT32$MBR_PT_FS_START, R9 ; find FS start sector
RSUB FAT32$READ_DW, 1
MOVE R0, R1 ; device handle
ADD FAT32$DEV_FS_LO, R1 ; FS start LBA low word
MOVE R10, @R1 ; store it in device hndl
MOVE R0, R1
ADD FAT32$DEV_FS_HI, R1 ; FS start LBA low word
MOVE R11, @R1 ; store it in device hndl
; ; Go to the first 512 byte sector of the file system (FS)
; ; and read it. For doing so, a 2 x 32bit multiplication
; ; needs to be utilized, because the FS start LBA
; ; FAT32$DEV_FS_LO / FAT32$DEV_FS_HI is 32bit and this needs
; ; to be multiplied by FAT32$SECTOR_SIZE (512) to obtain
; ; the linar address. But the upper two words of the 64bit
; ; result value need to be zero, otherwise the medium is too
; ; large to be handled by this FAT32 implementation
; MOVE R10, R8
; MOVE R11, R9
; MOVE FAT32$SECTOR_SIZE, R10
; MOVE 0, R11
; RSUB MULU32, 1
; CMP 0, R11
; RBRA _F32_MNT_SERR, !Z
; CMP 0, R10
; RBRA _F32_MNT_SERR, !Z
; RBRA _F32_MNT_DVID, 1
;_F32_MNT_SERR MOVE FAT32$ERR_SIZE, R9
; RBRA _F32_MNT_END, 1
MOVE R10, R8 ; LBA lo of 1st 512b sect.
MOVE R11, R9 ; LBA hi of 1st 512b sect.
; decode the FAT32 Volume ID (1st 512 byte sector)
; 1. sanity check: jump instruction to boot code
; 2. sanity check: word offs 22 must be zero on FAT32
; 3. check for magic
; 4. check for 512-byte sector size
; 5. check for two FATs
; 6. read sectors per fat and check if the hi word of sectors
; per fat is zero, as we otherwise would again run into
; multiplication problems (see above)
; 7. read root directory first cluster and check if the hi
; word is zero for the same reason
; 8. read sectors per cluster
; 9. read reserved sectors
_F32_MNT_DVID MOVE FAT32$DEV_BLOCK_READ, R10
MOVE R0, R11
RSUB FAT32$CALL_DEV, 1
CMP 0, R8
RBRA _F32_MNT_VID, Z
MOVE R8, R9
RBRA _F32_MNT_END, 1
_F32_MNT_VID MOVE R0, R8
MOVE FAT32$JMP1_OR_2_OFS, R9 ; sanity check: jump inst.
RSUB FAT32$READ_B, 1
CMP FAT32$JMP2, R10
RBRA _F32_MNT_SAN, Z
CMP FAT32$JMP1_1, R10
RBRA _F32_MNT_VERR, !Z
MOVE FAT32$JMP1_2_OFS, R9
RSUB FAT32$READ_B, 1
CMP FAT32$JMP1_2, R10
RBRA _F32_MNT_VERR, !Z
_F32_MNT_SAN MOVE FAT32$SANITY_OFS, R9
RSUB FAT32$READ_W, 1
CMP FAT32$SANITY, R10
RBRA _F32_MNT_MGC, Z
RBRA _F32_MNT_VERR, 1
_F32_MNT_MGC MOVE FAT32$MAGIC_OFS, R9 ; check magic
RSUB FAT32$READ_W, 1
CMP FAT32$MAGIC, R10
RBRA _F32_MNT_VERR, !Z
MOVE FAT32$SECTOR_SIZE_OFS, R9 ; check 512 byte sector
RSUB FAT32$READ_W, 1
CMP FAT32$SECTOR_SIZE, R10
RBRA _F32_MNT_VERR, !Z
MOVE FAT32$FATNUM_OFS, R9 ; check for two FATs
RSUB FAT32$READ_B, 1
CMP FAT32$FATNUM, R10
RBRA _F32_MNT_VERR, !Z
RBRA _F32_MNT_RVID, 1
_F32_MNT_VERR MOVE FAT32$ERR_NOFAT32, R9
RBRA _F32_MNT_END, 1
_F32_MNT_RVID MOVE R0, R8
MOVE FAT32$SECPERFAT_OFS, R9 ; read sectors per FAT
RSUB FAT32$READ_DW, 1
MOVE R10, R1 ; R1: sectors per FAT LO
MOVE R11, R2 ; R2: sectors per FAT HI
MOVE FAT32$ROOTCLUS_OFS, R9 ; read root dir 1st clus.
RSUB FAT32$READ_DW, 1
MOVE R10, R3 ; R3: rootdir 1st clus. LO
MOVE R11, R4 ; R4: rootdir 1st clus. HI
MOVE FAT32$SECPERCLUS_OFS, R9 ; read sectors per cluster
RSUB FAT32$READ_B, 1
MOVE R10, R5 ; R5: sectors per cluster
MOVE FAT32$RSSECCNT_OFS, R9 ; read number resvd. sec.
RSUB FAT32$READ_W, 1
MOVE R10, R6 ; R6: # reserved sectors
; calculate begin of FAT (LBA)
; fat_begin_lba = Partition_LBA_Begin + Num_of_Rsvd_Sectors
MOVE R0, R8
ADD FAT32$DEV_FS_LO, R8
MOVE @R8, R8 ; FS start: LO
MOVE R0, R9
ADD FAT32$DEV_FS_HI, R9
MOVE @R9, R9 ; FS start: HI
ADD R6, R8 ; 32bit add resvd. sect.
ADDC 0, R9
MOVE R0, R7
ADD FAT32$DEV_FAT_LO, R7 ; store it in device hndl
MOVE R8, @R7
MOVE R0, R7
ADD FAT32$DEV_FAT_HI, R7
MOVE R9, @R7
; calculate begin of clusters (LBA)
; clust_begin_lba = fat_begin_lba + (Num_FATs * Sect_per_FAT)
DECRB
MOVE R8, R0 ; save FAT start LO
MOVE R9, R1 ; save FAT start HI
INCRB
MOVE 2, R8 ; Num_FATs is hardcoded 2
MOVE 0, R9
MOVE R1, R10 ; sectors per fat LO
MOVE R2, R11 ; sectors per fat HI
RSUB MULU32, 1
CMP 0, R10
RBRA _F32_MNT_SERR, !Z
CMP 0, R11
RBRA _F32_MNT_SERR, !Z
DECRB
MOVE R0, R10 ; restore FAT start LO
MOVE R1, R11 ; restore FAT start HI
INCRB
ADD R10, R8 ; 32bit addition ..
ADDC R11, R9 ; .. result is in R9|R8
MOVE R0, R10
ADD FAT32$DEV_CLUSTER_LO, R10
MOVE R8, @R10 ; store result LO
MOVE R0, R10
ADD FAT32$DEV_CLUSTER_HI, R10
MOVE R9, @R10 ; store result HI
; store sectors per cluster and root directory 1st cluster
; and set currently active directory to root directory
MOVE R0, R10
ADD FAT32$DEV_SECT_PER_CLUS, R10
MOVE R5, @R10
MOVE R0, R10
ADD FAT32$DEV_RD_1STCLUS_LO, R10
MOVE R3, @R10
MOVE R0, R10
ADD FAT32$DEV_RD_1STCLUS_HI, R10
MOVE R4, @R10
MOVE R0, R10
ADD FAT32$DEV_AD_1STCLUS_LO, R10
MOVE R3, @R10
MOVE R0, R10
ADD FAT32$DEV_AD_1STCLUS_HI, R10
MOVE R4, @R10
; reset the FDH buffer filling tracker
MOVE R0, R10
ADD FAT32$DEV_BUFFERED_FDH, R10
XOR @R10, @R10
MOVE 0, R9 ; no errors occured
_F32_MNT_END MOVE @SP++, R12
MOVE @SP++, R11 ; restore registers
MOVE @SP++, R10
MOVE @SP++, R8
DECRB
DECRB
RET
_F32_MNT_SERR MOVE FAT32$ERR_SIZE, R9
RBRA _F32_MNT_END, 1
;
;*****************************************************************************
;* FAT32$DIR_OPEN opens the current directory for browsing
;*
;* Call this function, before working with FAT32$DIR_LIST. Directly after
;* mounting a device, the "current directory" equals the root directory.
;*
;* INPUT: R8 points to a valid device handle
;* R9 points to an empty directory handle struct that will be filled
;* (use FAT32$FDH_STRUCT_SIZE to reserve the memory)
;* OUTPUT: R8 points to the filled directory handle structure, i.e. it points
;* to where R9 originally pointed to
;* R9 0, if OK, otherwise error code
;*****************************************************************************
;
FAT32$DIR_OPEN INCRB
MOVE R9, R0 ; save device handle
ADD FAT32$FDH_DEVICE, R0
MOVE R8, @R0
MOVE R8, R0 ; save the cluster of ...
ADD FAT32$DEV_AD_1STCLUS_LO, R0 ; ... the current dir.
MOVE R9, R1
ADD FAT32$FDH_CLUSTER_LO, R1
MOVE @R0, @R1
MOVE @R0, R2
MOVE R8, R0
ADD FAT32$DEV_AD_1STCLUS_HI, R0
MOVE R9, R1
ADD FAT32$FDH_CLUSTER_HI, R1
MOVE @R0, @R1
MOVE @R0, R3
MOVE R9, R0 ; start with sector 0
ADD FAT32$FDH_SECTOR, R0
MOVE 0, @R0
MOVE R9, R0 ; start with index 0
ADD FAT32$FDH_INDEX, R0
MOVE 0, @R0
MOVE R9, R0 ; remember dir. handle
; remember the FDH that is responsible for filling the HW buf.
MOVE R8, R10
ADD FAT32$DEV_BUFFERED_FDH, R10
MOVE R9, @R10
; fill 512 byte hardware buffer with the first sector in clst.
MOVE R2, R9 ; low word of cluster
MOVE R3, R10 ; high word of cluster
MOVE 0, R11 ; sector
RSUB FAT32$READ_SIC, 1 ; R9 contains OK or error
MOVE R0, R8 ; return dir. handle
DECRB
RET
;
;*****************************************************************************
;* FAT32$DIR_LIST is used to browse the currently active directory
;*
;* This function is meant to be called iteratively to return one directory
;* entry after the other. It supports long filenames. The implementation
;* supports a maximum of 65.535 files within one folder.
;*
;* INPUT: R8 points to a valid directory handle (created by FAT32$DIR_OPEN)
;* R9 points to am empty directoy entry structure, having the size of
;* FAT32$DE_STRUCT_SIZE, that will be filled by this function.
;* R10 or-ed list of FAT32$FA_* flags to filter for certain types:
;* if the attribute is not set, then an entry having this flag
;* will not be browsed (i.e. it will be hidden). Use
;* FAT32$FA_DEFAULT, if you want to browse for all non hidden
;* files and directories.
;* OUTPUT: R8 still points to the directory handle
;* R9 points to the now filled directory entry structure
;* R10 1, if the current entry is a valid entry and therefore another
;* iteration (i.e. call to FAT32$DIR_LIST) makes sense
;* 0, if the current entry is not valid and therefore the end
;* of the directory has been reached (R11 is 0 in such a case)
;* R11 0, if OK, otherwise error code
;*****************************************************************************
;
FAT32$DIR_LIST INCRB ; if referenced in
MOVE R8, R0 ; comments, the registers
MOVE R9, R1 ; of this bank are
MOVE R12, R7 ; prefixed with a §
MOVE R10, R2 ; §R2 = attrib filters
; §R3 and §R4 used below
INCRB
MOVE R8, R0
ADD FAT32$FDH_DEVICE, R0
MOVE @R0, R0 ; R0 = device handle
MOVE R8, R1 ; R1 = directory handle
MOVE R9, R2 ; R2 = dir. entry struct.
; make sure that the 512-byte read-buffer contains the
; sector that is needed, and in parallel update the file and
; directory handle, if necessary
_F32_DLST_SKIP RSUB FAT32$READ_FDH, 1
CMP R9, 0
RBRA _F32_DLST_C1, Z
MOVE 0, R10
MOVE R9, R11
RBRA _F32_DLST_END, 1
; read the first byte (byte #0)
_F32_DLST_C1 MOVE R0, R8 ; R8 = device handle
MOVE R1, R3
ADD FAT32$FDH_INDEX, R3 ; R3 = pointer to index
MOVE @R3, R4 ; R4 = index aka byte addr
MOVE R4, R9 ; R9 = address of byte
RSUB FAT32$READ_B, 1 ; read byte to R10
; check if we reached the end of the directory
CMP R10, FAT32$FE_NOMORE ; last entry?
RBRA _F32_DLST_C2, !Z ; no: go on checking
MOVE 0, R10 ; yes, EOD reached
MOVE 0, R11
RBRA _F32_DLST_END, 1
; check, if the entry marks a deleted entry and if yes
; skip it
_F32_DLST_C2 CMP R10, FAT32$FE_DEL ; deleted entry?
RBRA _F32_DLST_C3, !Z ; no: go on checking
_F32_DLST_DS ADD FAT32$FE_SIZE, R4 ; set index to next record
MOVE R4, @R3 ; write back idx to FDH
MOVE R1, R8 ; R8 = directory handle
RBRA _F32_DLST_SKIP, 1 ; skip entry, read next
; special first character handling
_F32_DLST_C3 CMP R10, FAT32$FE_SPECIAL_CHAR ; special char?
RBRA _F32_DLST_C4, !Z ; no: go on
MOVE FAT32$FE_DEL, R10 ; yes: set the special chr
; store first read character to dir. entry struct.
_F32_DLST_C4 MOVE R2, R5 ; dir. entry struct.
ADD FAT32$DE_NAME, R5 ; R5 = offs. to name
MOVE R10, @R5 ; store character #0
; retrieve attributes and store them to the dir. entry struct.
MOVE R0, R8 ; R8 = device handle
ADD FAT32$FE_ATTRIB, R9 ; offset for entry attrib.
RSUB FAT32$READ_B, 1 ; read attribute to R10
MOVE R2, R12 ; store attribute
ADD FAT32$DE_ATTRIB, R12
MOVE R10, @R12
; long name?
MOVE R10, R7
AND FAT32$INT_LONG_MASK, R7
CMP R7, FAT32$INT_LONG_NAME
RBRA _F32_DLST_SN1, !Z ; no: short name
; long name: last entry starts with bit 6 set, if not
; then skip all long name entries until the next short
; name entry is there, or EOD
MOVE @R5, R7
MOVE FAT32$FE_LE_FINAL, R12
AND R12, R7
RBRA _F32_DLST_DS, Z ; no flag: skip entry
MOVE @R5, R7
NOT R12, R12 ; remove flag
AND R12, R7 ; R7 = # long name records
MOVE R7, R6 ; R6 = current record
; long name: check, if the record counter (byte #0) equals the
; current record, then retrieve the first checksum and
; store it in R11, all subsequent checksums need to be equal
; to R11, otherwise we skip the record until the
; next short name arrives
_F32_DLST_LN1 MOVE R0, R8
MOVE R4, R9
RSUB FAT32$READ_B, 1
AND R12, R10 ; remove start flag
CMP R10, R6 ; record cntr = cur. rec.?
RBRA _F32_DLST_LN1B, Z ; yes: go on
RBRA _F32_DLST_DS, !Z ; no: skip until short nm.
_F32_DLST_LN1B ADD FAT32$FE_LE_CHKSUM, R9 ; read checksum
RSUB FAT32$READ_B, 1
CMP R6, R7 ; first checksum?
RBRA _F32_DLST_LN2, !Z ; no
MOVE R10, R11 ; R11: checksum
_F32_DLST_LN2 CMP R10, R11 ; checksum OK?
RBRA _F32_DLST_DS, !Z ; no: skip until short nm.
; long name: retrieve the long file name that is stored at
; various offsets within the 32byte record and store it on the
; stack as this is a good mechanism of putting it together in
; the right order (it is stored the kind of backwards)
MOVE R5, R9
DECRB
MOVE R9, R5 ; §R5 = temp. stor. R5
SUB 13, SP ; max. 13 chars per record
MOVE SP, R6 ; §R6 = string buffer
INCRB
MOVE 5, R5 ; first 5 characters
MOVE R4, R9
ADD FAT32$FE_LE_C1_5, R9 ; first character
_F32_DLST_LN3 RSUB FAT32$READ_B, 1 ; read character
ADD 2, R9 ; ign. Unicode; ASCII only
DECRB
MOVE R10, @R6++ ; store character
INCRB
SUB 1, R5
RBRA _F32_DLST_LN3, !Z
MOVE 6, R5 ; another 6 characters
MOVE R4, R9
ADD FAT32$FE_LE_C6_11, R9 ; chars 6 .. 11
_F32_DLST_LN4 RSUB FAT32$READ_B, 1
ADD 2, R9
DECRB
MOVE R10, @R6++
INCRB
SUB 1, R5
RBRA _F32_DLST_LN4, !Z
MOVE 2, R5 ; another 2 characters
MOVE R4, R9
ADD FAT32$FE_LE_C12_13, R9 ; chars 12 .. 13
_F32_DLST_LN5 RSUB FAT32$READ_B, 1
ADD 2, R9
DECRB
MOVE R10, @R6++
INCRB
SUB 1, R5
RBRA _F32_DLST_LN5, !Z
DECRB ; restore original R5
MOVE R5, R9
INCRB
MOVE R9, R5
ADD FAT32$FE_SIZE, R4 ; update idx: next record
MOVE R4, @R3 ; store it to FDH
MOVE R1, R8 ; R8 = FDH
RSUB FAT32$READ_FDH, 1
MOVE @R3, R4 ; re-read due to READ_FDH
CMP 0, R9 ; check for error
RBRA _F32_DLST_LN6, Z ; no error: go on
MOVE R9, R12 ; error: restore stack
MOVE R7, R8 ; ((R7-R6)+1)*13 is the
SUB R6, R8 ; amount of memory to
ADD 1, R8 ; be reclaimed (max. 256)
MOVE 13, R9
SYSCALL(mulu, 1)
ADD R10, SP ; restore stack pointer
MOVE 0, R10 ; return invalid entry
MOVE R12, R11 ; return error code
RBRA _F32_DLST_END, 1
_F32_DLST_LN6 SUB 1, R6 ; next record
RBRA _F32_DLST_LN1, !Z
; copy long name from stack to directory entry structure
; this also restores the stack pointer
MOVE R11, R12
MOVE R7, R8 ; R7 = # long name records
MOVE 13, R9 ; 13 bytes per record
SYSCALL(mulu, 1) ; R10 = amount of bytes
_F32_DLST_LN7 MOVE @SP++, @R5++
SUB 1, R10
RBRA _F32_DLST_LN7, !Z
MOVE 0, @R5 ; add zero terminator
MOVE R12, R11
; read short name and calculate checksum
; this is a sanity check to find out, if the long filename
; really belongs to the short one, or if the long filename
; is an orphan; in the latter case: set the short filename
; as the definitive filename and discard the long filename
SUB 11, SP ; res. 11 byets on stack
MOVE SP, R6 ; R6: storate pointer
MOVE 11, R7 ; 8.3 filename = 11 chars
MOVE R0, R8 ; R8: device handle
MOVE R4, R9 ; R9: index
_F32_DLST_LN8 RSUB FAT32$READ_B, 1 ; read char to R10
MOVE R10, @R6++ ; store char on stack
ADD 1, R9 ; next index
SUB 1, R7 ; next char
RBRA _F32_DLST_LN8, !Z ; loop
MOVE SP, R8
RSUB FAT32$CHECKSUM, 1
ADD 11, SP ; restore stack pointer
CMP R8, R11 ; checksum OK?
RBRA _F32_DLST_C1, !Z ; discard orphan
; long name: we need to retrieve the attributes again,
; as only the last record has the correct attributes
MOVE R0, R8 ; R8 = device handle
MOVE R4, R9 ; R9 = current index
ADD FAT32$FE_ATTRIB, R9 ; offset for entry attrib.
RSUB FAT32$READ_B, 1 ; read attribute to R10
MOVE R2, R12 ; store attribute
ADD FAT32$DE_ATTRIB, R12
MOVE R10, @R12
; long name: apply attribute filter
DECRB
MOVE R2, R8 ; §R2 = attrib filter
INCRB
NOT R8, R8 ; the attribs not set..
AND R8, R10 ; ..shall be filtered out
RBRA _F32_DLST_DS, !Z ; so skip if != 0
; long name: go on and collect the other vital data
; that is common to long and short named entries
RBRA _F32_DLST_VITAL, 1
; short name: apply attribute filter
_F32_DLST_SN1 DECRB
MOVE R2, R8 ; §R2 = attrib filter
INCRB
NOT R8, R8 ; the attribs not set..
AND R8, R10 ; ..shall be filtered out
RBRA _F32_DLST_DS, !Z ; so skip if != 0
; short name: find out if the file name and/or the extension
; shall be displayed as lower case characters
MOVE R0, R8 ; R8 = device handle
MOVE R4, R9 ; R9 = index to be read
ADD FAT32$FE_DISPLAYCASE, R9
RSUB FAT32$READ_B, 1 ; R10 = read flags
DECRB ; store result in other
; register bank
MOVE 0, R3 ; §R3 = lower case name?
MOVE 0, R4 ; §R4 = lower case ext?
MOVE R10, R9 ; lower case name?
AND FAT32$FE_DC_NAME_MASK, R9
RBRA _F32_DLST_SN2, Z
MOVE 1, R3 ; yes: §R3 set to "true"
INCRB
MOVE R8, R12 ; lower case 1st character
MOVE @R5, R8
RSUB TO_LOWER, 1
MOVE R8, @R5
MOVE R12, R8
DECRB
_F32_DLST_SN2 MOVE R10, R9
AND FAT32$FE_DC_EXT_MASK, R9 ; lower case extension?
RBRA _F32_DLST_SN3, Z
MOVE 1, R4 ; yes: §R4 set to "true"
_F32_DLST_SN3 INCRB
; short name: retrieve it from index #1 (second character)
; because the char from index #0 has already been retrieved;
; the 8.3 filenames are stored as 11 bytes using 0x20 to
; fill/pad and the "." is not stored; as we already did
; read 1 byte, we still need to read 10 more bytes
ADD 1, R5 ; R5 points to name
MOVE 2, R6 ; char num (cnt from 1)
MOVE R4, R9 ; R9 = index to char #0
_F32_DLST_SBL1 ADD 1, R9 ; next character
RSUB FAT32$READ_B, 1 ; read byte to R10
CMP R6, 8 ; still in the name part?
RBRA _F32_DLST_SBL2, N ; no: extension part
DECRB
MOVE R3, R12 ; §R3 true? low. cs. name
INCRB
CMP R12, 1 ; lower case name?
RBRA _F32_DLST_SBL3, !Z ; no: go on
MOVE R8, R12 ; yes: convert to lower
MOVE R10, R8
RSUB TO_LOWER, 1
MOVE R8, R10
MOVE R12, R8
RBRA _F32_DLST_SBL3, 1
_F32_DLST_SBL2 DECRB
MOVE R4, R12 ; §R4 true? low. cs. ext
INCRB
CMP R12, 1 ; lower case extension?
RBRA _F32_DLST_SBL3, !Z ; no: go on
MOVE R8, R12 ; yes: convert to lower
MOVE R10, R8
RSUB TO_LOWER, 1
MOVE R8, R10
MOVE R12, R8
_F32_DLST_SBL3 CMP R10, FAT32$FE_PADDING ; padding characters?
RBRA _F32_DLST_SBL4, Z ; yes: ignore it
MOVE R10, @R5++ ; no: store character
_F32_DLST_SBL4 CMP R6, 8 ; add a "." after 8th chr
RBRA _F32_DLST_SBL5, !Z ; not the 8th character
MOVE '.', @R5++
_F32_DLST_SBL5 ADD 1, R6 ; one more char is read
CMP R6, 11 ; all chars read?
RBRA _F32_DLST_SBL1, !N ; one more to go?
MOVE 0, @R5 ; add zero terminator
; short name: if there is no file extension (i.e. the original
; characters 9, 10, 11 were 0x0020), we now have a 8.3 name
; that ends with a "." due to how the above mentioned logic
; works: it always adds a "." after the 8th character;
; as in 8.3 names no "." is allowed in a regular filename,
; it is safe to say in such a case: if the last character is
; a "." then delete this last character
MOVE R5, R8 ; points to zero term.
CMP @--R8, '.' ; ends with a "."?
RBRA _F32_DLST_VITAL, !Z ; no: go on
MOVE 0, @R8 ; yes: delete by adding a
; new zero terminator
; short name and long name: retrieve all the vital info:
; file size, last write timestamp, start cluster
_F32_DLST_VITAL NOP ; @TODO test corrupt directories
; includung orphans (e.g. by using wxHexEditor);
; file size
MOVE R0, R8 ; R8 = device handle
MOVE R4, R9 ; R9 = index to be read
ADD FAT32$FE_FILESIZE, R9
RSUB FAT32$READ_DW, 1 ; R10/R11 = lo/hi filesize
MOVE R2, R8 ; R8 = dir. entry. struct.
ADD FAT32$DE_SIZE_LO, R8 ; R8 => file size low word
MOVE R10, @R8 ; store file size low word
MOVE R2, R8
ADD FAT32$DE_SIZE_HI, R8 ; R8 => size high word
MOVE R11, @R8 ; store size high word
; file date
MOVE R0, R8 ; R8 = device handle
MOVE R4, R9 ; R9 = index to be read
ADD FAT32$FE_FILEDATE, R9
RSUB FAT32$READ_W, 1
MOVE R2, R8
ADD FAT32$DE_YEAR, R8 ; R8 = pointer to year
MOVE R10, R5 ; R5 = 16bit encoded date
SHR 9, R5 ; year is in bits 9..15
ADD 1980, R5 ; relative to 01/01/1980
MOVE R5, @R8 ; store year to dir. ent.
MOVE R2, R8
ADD FAT32$DE_MONTH, R8 ; R8 = pointer to month
MOVE R10, R5 ; R5 = 16bit encoded date
SHR 5, R5 ; month is in bits 5..8
AND 0x000F, R5 ; extract month only
MOVE R5, @R8 ; store month to dir. ent.
MOVE R2, R8
ADD FAT32$DE_DAY, R8 ; R8 = pointer to day
MOVE R10, R5 ; R5 = 16bit encoded date
AND 0x001F, R5 ; day is in bits 0..4
MOVE R5, @R8 ; store day to dir. entry
; file time
MOVE R0, R8 ; R8 = device handle
MOVE R4, R9 ; R9 = index to be read
ADD FAT32$FE_FILETIME, R9
RSUB FAT32$READ_W, 1
MOVE R2, R8
ADD FAT32$DE_HOUR, R8 ; R8 = pointer to hour
MOVE R10, R5 ; R5 = 16bit encoded time
SHR 11, R5 ; hour is in bits 11..15
MOVE R5, @R8 ; store hour to dir. ent.
MOVE R2, R8
ADD FAT32$DE_MINUTE, R8 ; R8 = pointer to minute
MOVE R10, R5 ; R5 = 16bit encoded time
SHR 5, R5 ; minute is in bits 5..10
AND 0x003F, R5
MOVE R5, @R8 ; store minute to dir. en.
MOVE R2, R8
ADD FAT32$DE_SECOND, R8 ; R8 = pointer to second
MOVE R10, R5 ; R5 = 16bit encoded time
AND 0x001F, R5 ; second is in bits 0..4
SHL 1, R5 ; seconds stored as 2 secs
MOVE R5, @R8 ; store seconds to dir. e.
; first cluster
MOVE R0, R8 ; R8 = device handle
MOVE R4, R9 ; R9 = index to be read
ADD FAT32$FE_CLUS_HI, R9
RSUB FAT32$READ_W, 1
MOVE R2, R8
ADD FAT32$DE_CLUS_HI, R8 ; R8 = pointer to clus. hi
MOVE R10, @R8 ; store clus. hi to d. en.
MOVE R0, R8 ; R8 = device handle
MOVE R4, R9 ; R9 = index to be read
ADD FAT32$FE_CLUS_LO, R9
RSUB FAT32$READ_W, 1
MOVE R2, R8
ADD FAT32$DE_CLUS_LO, R8 ; R8 = pointer to clus. lo
MOVE R10, @R8 ; store clus. lo to d. en.
; update index to next directory entry and return
ADD FAT32$FE_SIZE, R4 ; update index
MOVE R4, @R3 ; store it to FDH
MOVE 1, R10 ; return "valid entry"
MOVE 0, R11 ; return "no errors"
_F32_DLST_END DECRB ; restore R8, R9, R12
MOVE R0, R8
MOVE R1, R9
MOVE R7, R12
DECRB
RET
;
;*****************************************************************************
;* FAT32$FILE_OPEN opens a file for reading or writing or both
;*
;* You can either pass a simple file name or a complex nested path where the
;* last path segment is interpreted as a file name.
;* Consult the documentation of FAT32$CD to learn more about the meaning and
;* handling of the separator char.
;*
;* INPUT: R8 points to a valid device handle
;* R9 points to an empty file handle struct that will be filled
;* (use FAT32$FDH_STRUCT_SIZE to reserve the memory)
;* R10 points to a zero terminated filename string (path)
;* R11 separator char (if zero, then "/" will be used)
;* OUTPUT: R8 still points to the same handle
;* R9 points to the same address but this is now a valid/filled hndl
;* R10 0, if OK, otherwise error code
;*****************************************************************************
;
FAT32$FILE_OPEN INCRB
; save original registers
MOVE R9, R0 ; R0 = file handle struct
MOVE R11, R1
MOVE R0, R11 ; passing a non zero value
; means: operation mode
; is set to file open
MOVE R10, R9 ; R9: filename string
MOVE R1, R10 ; R10: separator char
RSUB FAT32$CD_OR_OF, 1 ; open file
MOVE R9, R10 ; return OK or err. in R10
RBRA _F32_FO_RET, !Z ; leave on error
; retrieve the first cluster of the file
MOVE R0, R2
ADD FAT32$FDH_CLUSTER_LO, R2
MOVE @R2, R9 ; R9 = cluster lo
MOVE R0, R2
ADD FAT32$FDH_CLUSTER_HI, R2
MOVE @R2, R10 ; R10 = cluster hi
XOR R11, R11 ; R11 = sector = 0
; special case: if the first cluster is zero, than we
; have a file of filesize zero; we can open it, and we can
; write to it, but if we would try to read from it, we would
; immediatelly get an EOF
CMP R9, R11
RBRA _F32_FO_READ, !Z
CMP R10, R11
RBRA _F32_FO_READ, !Z
MOVE 0, R10 ; no errors
RBRA _F32_FO_RET, 1 ; filesize is 0: return
; filesize is > 0, so read the first 512 byte sector of the file into the
; the internal buffer
_F32_FO_READ RSUB FAT32$READ_SIC, 1 ; read data
MOVE R9, R10 ; return OK or err. in R10
; remember FDH that was responsible for filling the HW buffer
MOVE R8, R9
ADD FAT32$DEV_BUFFERED_FDH, R9
MOVE R0, @R9
_F32_FO_RET MOVE R0, R9 ; restore org. registers
MOVE R1, R11
DECRB
RET
;
;*****************************************************************************
;* FAT32$FILE_RB reads one byte from an open file
;*
;* The read operation takes place at the current internal "seek position"
;* within the file, i.e. subsequent calls to FILE_RB will result in reading
;* byte per byte.
;*
;* INPUT: R8 points to a valid file handle
;* OUTPUT: R8 still points to the file handle
;* R9 low byte = currently read byte; high byte = 0
;* R10 0, if the read operation succeeded
;* FAT32$EOF, if the end of file has been reached; in this case
;* the value of R9 is 0 (means "undefined" here)
;* any other error code in case of an error
;*****************************************************************************
;
FAT32$FILE_RB INCRB
; check for eof by comparing the already read amount of bytes
; with the filesize (we need to do this, because we cannot
; rely on the FAT last cluster marker, because a file can be
; obviously smaller than a cluster)
MOVE R8, R0
ADD FAT32$FDH_READ_LO, R0
MOVE R8, R1
ADD FAT32$FDH_SIZE_LO, R1
CMP @R0, @R1
RBRA _F32_FRB_START, !Z
MOVE R8, R0
ADD FAT32$FDH_READ_HI, R0
MOVE R8, R1
ADD FAT32$FDH_SIZE_HI, R1
CMP @R0, @R1
RBRA _F32_FRB_START, !Z
MOVE FAT32$EOF, R10 ; return EOF
RBRA _F32_FRB_RET, 1
; follow the FAT cluster chain and adjust cluster/sector/index
; within the handle, if necessary; that means that also
; new sectors will be read, if necessary
_F32_FRB_START RSUB FAT32$READ_FDH, 1
MOVE R9, R10 ; R10 is the return value
RBRA _F32_FRB_RET, !Z ; return on error
; read the byte
MOVE R8, R7 ; save original R8
ADD FAT32$FDH_DEVICE, R8
MOVE @R8, R8 ; retrieve device handle
MOVE R7, R9
ADD FAT32$FDH_INDEX, R9
MOVE @R9, R9 ; retrieve the read index
RSUB FAT32$READ_B, 1 ; read one byte
MOVE R7, R8 ; restore original R8
MOVE R10, R9 ; R9 = read byte = retval
MOVE 0, R10 ; R10 = 0 = no error
; increase the index and the read size as we just successfully
; did read read one byte
MOVE R8, R0
ADD FAT32$FDH_INDEX, R0
ADD 1, @R0 ; increase index by 1
MOVE R8, R0
ADD FAT32$FDH_READ_LO, R0
MOVE R8, R1
ADD FAT32$FDH_READ_HI, R1
ADD 1, @R0 ; 32bit add 1 to read size
ADDC 0, @R1
_F32_FRB_RET DECRB
RET
;
;*****************************************************************************
;* FAT32$FILE_SEEK positions the read/write pointer within an open file
;*
;* INPUT: R8 points to a valid file handle
;* R9 LO word of seek position
;* R10 HI word of seek position
;* OUTPUT: R8 still points to file handle
;* R9 0, if OK, otherwise error code
;*****************************************************************************
;
FAT32$FILE_SEEK INCRB
MOVE R8, R0 ; R0 = file handle
MOVE R9, R1 ; R2|R1 = HI|LO seek pos
MOVE R10, R2
MOVE R11, R3
; if the seek position is zero, then skip the function
CMP 0, R1
RBRA _F32_FS_CHKSIZ, !Z
CMP 0, R2
RBRA _F32_FS_CHKSIZ, !Z
MOVE 0, R9 ; no error
RBRA _F32_FS_RET, 1
; if the seek position is larger than the file size,
; then skip the function;
; the 32bit compare is done via a 32bit sub and then
; checking for a negative result via the carry flag
_F32_FS_CHKSIZ MOVE R0, R4
ADD FAT32$FDH_SIZE_LO, R4
MOVE @R4, R4 ; file size LO
MOVE R0, R5
ADD FAT32$FDH_SIZE_HI, R5 ; file size HI
MOVE @R5, R5
SUB R1, R4
SUBC R2, R5
RBRA _F32_FS_START, !C ; seekpos <= filesize
MOVE FAT23$ERR_SEEKTOOLARGE, R9
RBRA _F32_FS_RET, 1
; 1. divide the 32bit seek position by 512
; 2. push the index forward <quotient> times and then
; 3. set the the new index to the <modulo>
; divide the 32bit seek position by 512
_F32_FS_START MOVE R1, R8 ; R9|R8 = HI|LO dividend
MOVE R2, R9
MOVE FAT32$SECTOR_SIZE, R10 ; R11|R10 = HI|LO divisor
XOR R11, R11
RSUB DIVU32, 1
; push the index forward R9|R8 = <quotient> times
; pushing is done by setting the index to the sector size
; (which is normally 512) and then using FAT32$READ_FDH
MOVE R8, R4 ; R5|R4 = HI|LO of the ..
MOVE R9, R5 ; 32bit amount to push
_F32_FS_LOOP CMP R4, 0 ; still anything to push?
RBRA _F32_FS_IPUSH, !Z
CMP R5, 0
RBRA _F32_FS_IPUSH, !Z
RBRA _F32_FS_INDEX, 1
_F32_FS_IPUSH SUB 1, R4 ; 32bit sub 1 from R5|R4
SUBC 0, R5
MOVE R0, R6
ADD FAT32$FDH_INDEX, R6
MOVE FAT32$SECTOR_SIZE, @R6 ; set index to push mode
MOVE R0, R8
RSUB FAT32$READ_FDH, 1
CMP 0, R9 ; error?
RBRA _F32_FS_IPUSH2, Z ; no: continue
RBRA _F32_FS_RET, 1 ; yes: quit
_F32_FS_IPUSH2 MOVE R0, R6 ; 32bit add the amount ..
ADD FAT32$FDH_READ_LO, R6 ; .. of read bytes to ..
MOVE R0, R7 ; .. the file handle
ADD FAT32$FDH_READ_HI, R7
ADD FAT32$SECTOR_SIZE, @R6
ADDC 0, @R7
RBRA _F32_FS_LOOP, 1 ; next iteration
; set the new index to the <modulo>
_F32_FS_INDEX MOVE R0, R6
ADD FAT32$FDH_INDEX, R6
MOVE R10, @R6 ; set index to modulo
MOVE R0, R6 ; 32bit add the amount ..
ADD FAT32$FDH_READ_LO, R6 ; .. of read bytes to ..
MOVE R0, R7 ; .. the file handle
ADD FAT32$FDH_READ_HI, R7
ADD R10, @R6
ADDC 0, @R7
MOVE 0, R9 ; no error
_F32_FS_RET MOVE R0, R8 ; restore R8 and R10
MOVE R2, R10
MOVE R3, R11
DECRB
RET
;
;*****************************************************************************
;* FAT32$CD changes the current directory
;*
;* This function supports traversing deeply in one call using nested
;* directories like "dir1/dir2/dir3/dir4". Any non-zero separator char
;* can be used. It is important to pass a separator char, even if you do not
;* plan to pass nested directories. If in doubt, just pass 0x002F, which is
;* the ASCII code for / (passing 0x0000 also leads to 0x002F being used).
;*
;* A separator char as the very first char (e.g. "/dir1/dir2") means, that
;* we start searching from the root directory. No separator char at the
;* beginning means, that we work relative to the current directory. The FAT32
;* typical "." and ".." work as expected.
;*
;* FAT32 is case preserving but not case sensitive.
;*
;* INPUT: R8 points to a valid device handle
;* R9 points to a zero terminated directory string (path)
;* R10 separator char (if zero, then "/" will be used)
;* OUTPUT: R8 still points to the directory handle
;* R9 0, if OK, otherwise error code
;*****************************************************************************
;
FAT32$CD INCRB
MOVE R11, R0
XOR R11, R11 ; operation mode = CD
RSUB FAT32$CD_OR_OF, 1
MOVE R0, R11
DECRB
RET
;
;*****************************************************************************
;* FAT32$PRINT_DE is a pretty printer for directory entries
;*
;* Uses monitor (system) stdout to print. Allows the configuration of the
;* amount of data that shall be printed: Filename only is the minimum.
;* Additionally attributes, file sizes, file date and file time can be shown.
;* The printed layout is as follows:
;*
;* <DIR> HRSA BBBBBBBBB YYYY-MM-DD HH:MM name...
;*
;* <DIR> means that the entry is a directory, otherwise whitespace
;* H = hidden flag
;* R = read only flag
;* S = system flag
;* A = archive flag
;* BBBBBBBBB = decimal size of the file in bytes
;* YYYY-MM-DD = file date
;* HH:MM = file time
;* name... = file name in long file format
;*
;* INPUT: R8: pointer to directy entry structure
;* R9: print flags as defined in FAT32$PRINT_SHOW_*
;*****************************************************************************
;
FAT32$PRINT_DE INCRB
MOVE R10, R0 ; save R10 .. R12
MOVE R11, R1
MOVE R12, R2
INCRB
MOVE R8, R0 ; R0 = ptr to dir. ent. s.
MOVE R9, R1 ; R1 = print flags
; print <DIR> indicator
MOVE R1, R2 ; show <dir> indicator?
AND FAT32$PRINT_SHOW_DIR, R2
RBRA _F32_PDE_A1, Z ; no: go on
MOVE R0, R2 ; is current entry a dir.?
ADD FAT32$DE_ATTRIB, R2
MOVE @R2, R2
MOVE FAT32$PRINT_DE_DIR_N, R8 ; assume no
AND FAT32$FA_DIR, R2
RBRA _F32_PDE_D1, Z
MOVE FAT32$PRINT_DE_DIR_Y, R8 ; yes, it is
_F32_PDE_D1 SYSCALL(puts, 1) ; print <DIR> or whitespc
; print attributes
_F32_PDE_A1 MOVE R1, R2 ; show attributes?
AND FAT32$PRINT_SHOW_ATTRIB, R2
RBRA _F32_PDE_S1, Z ; no: go on
MOVE R0, R2
ADD FAT32$DE_ATTRIB, R2
MOVE @R2, R3 ; @R2 contains attrib
MOVE FAT32$PRINT_DE_AN, R8
AND FAT32$FA_ARCHIVE, R3 ; attrib = archive?
RBRA _F32_PDE_A2, Z
MOVE FAT32$PRINT_DE_AA, R8
_F32_PDE_A2 SYSCALL(puts, 1)
MOVE @R2, R3 ; @R2 contains attrib
MOVE FAT32$PRINT_DE_AN, R8
AND FAT32$FA_HIDDEN, R3 ; attrib = hidden?
RBRA _F32_PDE_A3, Z
MOVE FAT32$PRINT_DE_AH, R8
_F32_PDE_A3 SYSCALL(puts, 1)
MOVE @R2, R3 ; @R2 contains attrib
MOVE FAT32$PRINT_DE_AN, R8
AND FAT32$FA_READ_ONLY, R3 ; attrib = read only?
RBRA _F32_PDE_A4, Z
MOVE FAT32$PRINT_DE_AR, R8
_F32_PDE_A4 SYSCALL(puts, 1)
MOVE @R2, R3 ; @R2 contains attrib
MOVE FAT32$PRINT_DE_AN, R8
AND FAT32$FA_SYSTEM, R3 ; attrib = system?
RBRA _F32_PDE_A5, Z
MOVE FAT32$PRINT_DE_AS, R8
_F32_PDE_A5 SYSCALL(puts, 1)
MOVE FAT32$PRINT_DE_AN, R8 ; print space
SYSCALL(puts, 1)
; print size
_F32_PDE_S1 MOVE R1, R2 ; show file size?
AND FAT32$PRINT_SHOW_SIZE, R2
RBRA _F32_PDE_DATE, Z ; no: go on
MOVE R0, R2 ; is current entry a dir.?
ADD FAT32$DE_ATTRIB, R2
MOVE @R2, R2
AND FAT32$FA_DIR, R2
RBRA _F32_PDE_S2, Z ; no: print file size
MOVE FAT32$PRINT_DE_DIR_S, R8 ; yes: print spaces ...
SYSCALL(puts, 1) ; ... instead of file size
RBRA _F32_PDE_DATE, 1
_F32_PDE_S2 MOVE R0, R8 ; R8 = dir. entry struct.
ADD FAT32$DE_SIZE_LO, R8 ; retrieve LO/HI of ...
MOVE @R8, R8 ; ... filesize in R8/R9
MOVE R0, R9
ADD FAT32$DE_SIZE_HI, R9
MOVE @R9, R9
XOR R7, R7 ; print trailing spaces
RSUB _F32_PDE_PD, 1 ; print decimal filesize
MOVE FAT32$PRINT_DE_AN, R8 ; print space
SYSCALL(puts, 1)
; print date
_F32_PDE_DATE MOVE 1, R7 ; do not print trailing sp
MOVE R1, R2 ; show date?
AND FAT32$PRINT_SHOW_DATE, R2
RBRA _F32_PDE_TIME, Z ; no: go on
MOVE R0, R8
ADD FAT32$DE_YEAR, R8
MOVE @R8, R8 ; R8 = year
XOR R9, R9 ; high word = zero
RSUB _F32_PDE_PD, 1 ; print year as decimal
MOVE FAT32$PRINT_DE_DATE, R8 ; print separator
SYSCALL(puts, 1)
MOVE R0, R8
ADD FAT32$DE_MONTH, R8
MOVE @R8, R8 ; R8 = month
XOR R9, R9 ; hi word zero
RSUB _F32_PDE_PD, 1 ; print month as decimal
MOVE FAT32$PRINT_DE_DATE, R8 ; print separator
SYSCALL(puts, 1)
MOVE R0, R8
ADD FAT32$DE_DAY, R8
MOVE @R8, R8 ; R8 = day
XOR R9, R9 ; hi word zero
RSUB _F32_PDE_PD, 1 ; print day as decimal
MOVE FAT32$PRINT_DE_AN, R8 ; print space
SYSCALL(puts, 1)
; print time
_F32_PDE_TIME MOVE 1, R7 ; do not print trailing sp
MOVE R1, R2 ; show time?
AND FAT32$PRINT_SHOW_TIME, R2
RBRA _F32_PDE_N1, Z ; no: go on
MOVE R0, R8
ADD FAT32$DE_HOUR, R8
MOVE @R8, R8 ; R8 = hour
XOR R9, R9 ; high word = zero
RSUB _F32_PDE_PD, 1 ; print hour as decimal
MOVE FAT32$PRINT_DE_TIME, R8 ; print separator
SYSCALL(puts, 1)
MOVE R0, R8
ADD FAT32$DE_MINUTE, R8
MOVE @R8, R8 ; R8 = minute
XOR R9, R9 ; high word = zero
RSUB _F32_PDE_PD, 1 ; print minute as decimal
MOVE FAT32$PRINT_DE_AN, R8 ; print space
SYSCALL(puts, 1)
; print name
_F32_PDE_N1 MOVE R0, R8
ADD FAT32$DE_NAME, R8
SYSCALL(puts, 1) ; print name
SYSCALL(crlf, 1) ; next line out stdout
_F32_PDE_END MOVE R0, R8 ; restore R8 .. R11
MOVE R1, R9
DECRB
MOVE R0, R10
MOVE R1, R11
MOVE R2, R12
DECRB
RET
; sub-sub routine to print a decimal that is in HI|LO=R9|R8
; if R7 = 0 then then print trailing spaces
; if R7 = 1 then numbers < 10 will receive one trailing zero
; but no trailing spaces at all
_F32_PDE_PD SUB 11, SP ; create 11 bytes memory..
MOVE SP, R10 ; ..area on the stack
RSUB H2D_ASCII, 1 ; make decimal string
CMP R7, 0 ; print trailing spaces?
RBRA _F32_PDE_PDNT, !Z ; no
MOVE R10, R8 ; yes: use R10 to print
RBRA _F32_PDE_PDPS, 1 ; print
_F32_PDE_PDNT CMP R12, 1 ; only one digit?
RBRA _F32_PDE_PDOD, !Z ; no: go on
MOVE 0x0030, @--R11 ; yes: add trailing zero
_F32_PDE_PDOD MOVE R11, R8 ; use R10 to print
_F32_PDE_PDPS SYSCALL(puts, 1) ; print decimal
ADD 11, SP ; restore stack
RET
;
;*****************************************************************************
;* FAT32$CALL_DEV calls a device management function
;*
;* INPUT: R8, R9 are the parameters to the function
;* R10 is the function index
;* R11 is the mount data structure (device handle)
;*
;* OUTPUT: R8 is the return value from the function
;*****************************************************************************
;
FAT32$CALL_DEV INCRB
MOVE R11, R0 ; compute function address
ADD R10, R0
MOVE _F32$CDEVEND, @--SP ; compute return address
MOVE @R0, PC ; perform function call
_F32$CDEVEND DECRB
RET
;
;*****************************************************************************
;* FAT32$CD_OR_OF changes the current directory or opens a file
;*
;* This is an internal help function, because changing a directory and
;* opening a file requires very similar actions. See the documentation for
;* FAT32$CD and FAT32$FILE_OPEN for details.
;*
;* INPUT: R8 points to a valid device handle
;* R9 points to a zero terminated string (directory or filename)
;* R10 separator char (if zero, then "/" will be used)
;* R11 operation mode: 0 = FAT32$CD, otherwise FAT32$FILE_OPEN
;* in the latter case, R11 points to the file handle structure
;* that will be filled by FAT32$CDOF.
;* OUTPUT: R9 0, if OK, otherwise error code
;* all other registers remain unchanged
;*****************************************************************************
;
FAT32$CD_OR_OF INCRB
MOVE R10, R0 ; save original registers
MOVE R11, R1
MOVE R12, R2
; save original active directory so that we can restore
; it in case of an error, so that a CD or FILE_OPEN to a
; non existing location or file does not lead to a changed
; AD pointer within the device handle
MOVE R8, R3 ; §R3 = org. AD cluster LO
ADD FAT32$DEV_AD_1STCLUS_LO, R3
MOVE @R3, R3
MOVE R8, R4
ADD FAT32$DEV_AD_1STCLUS_HI, R4
MOVE @R4, R4 ; $R4 = org. AD cluster HI
INCRB
MOVE R8, R0 ; R0 = device handle
MOVE R9, R1 ; R1 = path
MOVE R11, R12 ; R12 = op. mode/file handle
; if no separator char is given, then use /
CMP R10, 0
RBRA _F32_DF_1, !Z
MOVE '/', R10
; if the first character in the path is a separator char,
; then change the current working directory (AD) to
; root (RD) and skip the separator char
; (just in case there are multiple separator chars at the
; beginning, i.e. more than just one: these will be filtered
; out by the SPLIT function later)
_F32_DF_1 CMP @R1, R10
RBRA _F32_DF_2, !Z
ADD 1, R1 ; skip first character
RSUB _F32_CDROOT, 1 ; change to root
; split the path into segments and build an outer loop
; around the next section (_F32_DF_NXSG) that iteratively
; dives into the full path consisting of the split segments
_F32_DF_2 MOVE R1, R8
MOVE R10, R9
RSUB SPLIT, 1
MOVE R9, R2 ; R2 = stack pointer restore
CMP R8, 0
RBRA _F32_DF_ENDNR, Z ; path is empty: end
MOVE R8, R3 ; R3 = amount of segments
MOVE SP, R4 ; R4 = current path segment
; reserve memory for the directory handle (FDH) and for the
; directory entry handle (DE) on the stack
SUB FAT32$FDH_STRUCT_SIZE, SP
MOVE SP, R5 ; R5 = directory handle
SUB FAT32$DE_STRUCT_SIZE, SP
MOVE SP, R6 ; R6 = directory entry handle
; change directory into current path segment:
; 1. open directory, create directory handle
; 2. browse directory entries to find the current path segm.:
; a) check for entries that are of type FAT32$FA_DIR
; b) compare directory name (case insensitive)
; 3. use start cluster of found entry as new AD entry
; in case of a file (R12 != 0)
; work similar to the case of the directory, but treat
; the last path segment as a filename
_F32_DF_NXSG ADD 1, R4 ; skip length info of path
MOVE R0, R8 ; R8 = R0 = device handle
MOVE R5, R9 ; R9 = R5 = directory handle
RSUB FAT32$DIR_OPEN, 1 ; open directory for browsing
CMP R9, 0 ; errors?
RBRA _F32_DF_ENDWR, !Z ; return error in R9
_F32_DF_LNX MOVE R5, R8 ; R8 = R5 = directory handle
MOVE R6, R9 ; R9 = R6 = dir. entry handle
MOVE FAT32$FA_ALL, R10 ; flags: "browse everything"
RSUB FAT32$DIR_LIST, 1 ; browse next entry
CMP R11, 0 ; errors?
RBRA _F32_DF_NXSG3, Z ; no errors
MOVE R11, R9
RBRA _F32_DF_ENDWR, 1 ; return error
_F32_DF_NXSG3 CMP R10, 0 ; file or directory not found
RBRA _F32_DF_NXSG4, !Z ; we can go on
CMP R12, 0 ; error: return & restore AD
RBRA _F32_DF_NXERRD, Z ; operation mode = CD?
MOVE FAT32$ERR_FILENOTFOUND, R9 ; error: file not found
RBRA _F32_DF_ENDWR, 1 ; return error
_F32_DF_NXERRD MOVE FAT32$ERR_DIRNOTFOUND, R9 ; error: directory not found
RBRA _F32_DF_ENDWR, 1 ; return error
_F32_DF_NXSG4 MOVE R6, R7 ; chk attrib depending on R12
ADD FAT32$DE_ATTRIB, R7
MOVE @R7, R7 ; R7 now contains the attrib
CMP R12, 0 ; operation mode = CD?
RBRA _F32_DF_CFCD, Z ; yes: check for directory
CMP R3, 1 ; no: last path segment?
RBRA _F32_DF_CFCD, !Z ; no: so treat it as a path
AND FAT32$FA_DIR, R7 ; yes: now we check for a file
RBRA _F32_DF_LNX, !Z ; no file but dir: try next DE
RBRA _F32_DF_NXSG5, 1 ; go on with the name compare
_F32_DF_CFCD AND FAT32$FA_DIR, R7 ; directory flag set?
RBRA _F32_DF_LNX, Z ; no directory: try next DE
_F32_DF_NXSG5 MOVE R6, R7 ; R7 = dir. entry name
ADD FAT32$DE_NAME, R7
MOVE R7, R8
SYSCALL(str2upper, 1) ; dir. entry name uppercase
MOVE R4, R8
SYSCALL(str2upper, 1) ; current path segm. uppercase
MOVE R7, R9
SYSCALL(strcmp, 1) ; compare DE with current path
CMP R10, 0
RBRA _F32_DF_LNX, !Z ; no match: try next DE
; this code is executed, when the directory entry (DE)
; matches the current path segment; in such a case,
; we need to distinguish: are we currently doing a CD
; or are we opening a file: in the latter case: if the current
; segment is the last one, then this is the actual file, so
; we are not "CDing" in subdirectories any more (i.e. not
; modifying the AD any more), but we are returning the start
; cluster of the file and its filesize within the file handle
; structure that is passed in R12
CMP R12, 0 ; operation mode = CD?
RBRA _F32_DF_MATCHD, Z ; yes: so do the CD
CMP R3, 1 ; is it the last path segment?
RBRA _F32_DF_MATCHD, !Z ; no: so do the CD
; fill the file handle structure and return with a success
MOVE R12, R7
ADD FAT32$FDH_DEVICE, R7
MOVE R0, @R7 ; set the device handle
MOVE R12, R7
ADD FAT32$FDH_CLUSTER_LO, R7
MOVE R6, R8
ADD FAT32$DE_CLUS_LO, R8
MOVE @R8, @R7 ; set the cluster lo word
MOVE R12, R7
ADD FAT32$FDH_CLUSTER_HI, R7
MOVE R6, R8
ADD FAT32$DE_CLUS_HI, R8
MOVE @R8, @R7 ; set the cluster hi word
MOVE R12, R7
ADD FAT32$FDH_SIZE_LO, R7
MOVE R6, R8
ADD FAT32$DE_SIZE_LO, R8
MOVE @R8, @R7 ; set the filesize lo word
MOVE R12, R7
ADD FAT32$FDH_SIZE_HI, R7
MOVE R6, R8
ADD FAT32$DE_SIZE_HI, R8
MOVE @R8, @R7 ; set the filesize hi word
XOR R8, R8 ; R8 = 0
MOVE R12, R7
ADD FAT32$FDH_SECTOR, R7
MOVE R8, @R7 ; set the start sector to 0
MOVE R12, R7
ADD FAT32$FDH_INDEX, R7
MOVE R8, @R7 ; set the start index to 0
MOVE R12, R7
ADD FAT32$FDH_READ_LO, R7
MOVE R8, @R7 ; already read size LO = 0
MOVE R12, R7
ADD FAT32$FDH_READ_HI , R7
MOVE R8, @R7 ; already read size HI = 0
RBRA _F32_DF_SUCCESS, 1 ; return a success
; change the directory by setting AD within the device handle
_F32_DF_MATCHD MOVE R6, R7 ; match! set AD to new cluster
ADD FAT32$DE_CLUS_LO, R7
MOVE R0, R8
ADD FAT32$DEV_AD_1STCLUS_LO, R8
MOVE @R7, @R8 ; perform the actual CD (low)
MOVE R6, R7
ADD FAT32$DE_CLUS_HI, R7
MOVE R0, R8
ADD FAT32$DEV_AD_1STCLUS_HI, R8
MOVE @R7, @R8 ; perform the actual CD (high)
; there is a speciality in the FAT32 specification:
; in case of a ".." that points to root, FAT32$DE_CLUS_LO
; and FAT32$DE_CLUS_HI will be zero (i.e. illegal); therefore
; we need to handle this special case
CMP @R7, 0 ; FAT32$DE_CLUS_HI = 0?
RBRA _F32_DF_SUCCESS, !Z ; no: go on
MOVE R6, R7
ADD FAT32$DE_CLUS_LO, R7
CMP @R7, 0 ; FAT32$DE_CLUS_LO = 0?
RBRA _F32_DF_SUCCESS, !Z ; no: go on
MOVE R6, R7
ADD FAT32$DE_NAME, R7
CMP @R7++, '.' ; first "." of ".."?
RBRA _F32_DF_SUCCESS, !Z ; no: go on
CMP @R7, '.' ; second "." of ".."?
RBRA _F32_DF_SUCCESS, !Z ; no: go on
MOVE R6, R9 ; yes: cd to root, then go on
RSUB _F32_CDROOT, 1
MOVE R9, R6
_F32_DF_SUCCESS MOVE 0, R9 ; operation was successful
; loop if there is another segment left
SUB 1, R3 ; one less path segment
RBRA _F32_DF_ENDWR, Z ; end if no more path segmts.
ADD @--R4, R4 ; R4 was incremented to skip..
; ..the length information, ..
; ..so we need to predecr. ..
; ..and then increase the ..
; ..pointer to the next segm.
; it now points to the '0' of
; the current string
ADD 1, R4 ; now it points to the next ..
; .. segment, i.e. to the ..
; length information
RBRA _F32_DF_NXSG, 1 ; process next path segment
; restore SP
_F32_DF_ENDWR ADD FAT32$FDH_STRUCT_SIZE, SP
ADD FAT32$DE_STRUCT_SIZE, SP
_F32_DF_ENDNR ADD R2, SP ; restore stack pointer
MOVE R0, R8 ; restore original R8
DECRB
; in the case that we are in the file open mode (R12 != 0)
; or case of an error: restore the original active dir. (AD)
CMP R12, 0 ; are we in file open mode?
RBRA _F32_DF_RESTAD, !Z ; yes: restore AD
CMP R9, 0 ; was there an error?
RBRA _F32_DF_RET, Z ; no error: return
_F32_DF_RESTAD MOVE R8, R7
ADD FAT32$DEV_AD_1STCLUS_LO, R7
MOVE R3, @R7
MOVE R8, R7
ADD FAT32$DEV_AD_1STCLUS_HI, R7
MOVE R4, @R7
MOVE R0, R10 ; restore original registers
MOVE R1, R11
MOVE R2, R12
_F32_DF_RET DECRB
RET
; sub-sub routine to change the AD to the RD
_F32_CDROOT MOVE R0, R6
ADD FAT32$DEV_RD_1STCLUS_LO, R6
MOVE R0, R7
ADD FAT32$DEV_AD_1STCLUS_LO, R7
MOVE @R6, @R7 ; AD LO cluster = RD LO clust.
MOVE R0, R6
ADD FAT32$DEV_RD_1STCLUS_HI, R6
MOVE R0, R7
ADD FAT32$DEV_AD_1STCLUS_HI, R7
MOVE @R6, @R7 ; AD HI cluster = RD HI clust.
RET
;
;*****************************************************************************
;* FAT32$READ_FDH fills the read buffer according to the current index in FDH
;*
;* If the index within FDH is < FAT32$SECTOR_SIZE (512), then it is assumed,
;* that no read operation needs to be performed, i.e. another function has
;* already filled the 512-byte read-buffer. Otherwise, the sector is
;* increased (and the index is reset to 0) and if necessary also the cluster
;* is increased (and the sector is reset to 0). The new index, sector and
;* cluster values are stored within the FDH (file and directory handle).
;* In case of an increased index or sector or cluster value, the 512-byte
;* read-buffer is re-read for subsequent read accesses.
;*
;* The above-mentioned "assumed that no read operation needs to be performed"
;* is only true, if the FDH, which was originally responsible for filling the
;* hardware buffer is the same, as the one who is currently active. This is
;* checked by evaluating FAT32$DEV_BUFFERED_FDH.
;*
;* INPUT: R8: FDH
;* OUTPUT: R8: FDH
;* R9: 0, if OK, otherwise error code
;*****************************************************************************
;
FAT32$READ_FDH INCRB
MOVE R8, R0
MOVE R10, R1
MOVE R11, R2
MOVE R12, R3
INCRB
MOVE R8, R0
ADD FAT32$FDH_DEVICE, R0
MOVE @R0, R0 ; R0 = device handle
MOVE R8, R1 ; R1 = FDH
; @TODO: as soon as this function is generalized to also
; be able to write / append, then also files with an initial
; size of zero (1st clst. = 0) must be allowed to be appended
; if the first cluster = 0 then we try to read from an
; illegal cluster, therefore exit with an error message
XOR R3, R3
MOVE R1, R2
ADD FAT32$FDH_CLUSTER_LO, R2
CMP @R2, R3
RBRA _F32_RFDH_START, !Z
MOVE R1, R2
ADD FAT32$FDH_CLUSTER_HI, R2
CMP @R2, R3
RBRA _F32_RFDH_START, !Z
MOVE FAT32$ERR_ILLEGAL_CLUS, R9
RBRA _F32_RFDH_END, 1
; if the current FDH (R1) is not equal to the one, which
; filled the 512 byte hardware buffer, then we need to
; re-read the 512 byte hardware buffer again
_F32_RFDH_START MOVE R0, R2
ADD FAT32$DEV_BUFFERED_FDH, R2
CMP R1, @R2
RBRA _F32_RFDH_STRT2, Z ; cur. FDH == responsible
MOVE R1, R2
ADD FAT32$FDH_CLUSTER_LO, R2
MOVE @R2, R9
MOVE R1, R2
ADD FAT32$FDH_CLUSTER_HI, R2
MOVE @R2, R10
MOVE R1, R2
ADD FAT32$FDH_SECTOR, R2
MOVE @R2, R11
MOVE R0, R8
RSUB FAT32$READ_SIC, 1 ; re-read hardware buffer
MOVE R0, R2
ADD FAT32$DEV_BUFFERED_FDH, R2
MOVE R1, @R2 ; remember responsible FDH
CMP R9, 0
RBRA _F32_RFDH_END, !Z ; exit on error
; if the current "to-be-read" index equals 512, then
; we need to read the next sector within the cluster
_F32_RFDH_STRT2 MOVE R1, R2
ADD FAT32$FDH_INDEX, R2
MOVE @R2, R3 ; R3 = "to-be-read" index
CMP R3, FAT32$SECTOR_SIZE
RBRA _F32_RFDH_CISS, !Z
; reset the index and increase the sector
; if the sector is larger than the sectors per cluster, then
; we need to increase the cluster (i.e. look up the next one
; in the FAT); otherwise we can just read the new sector
; within the same cluster
MOVE 0, @R2 ; write back resetted idx
MOVE 0, R3 ; R3 = "to-be-read" index
MOVE R1, R2
ADD FAT32$FDH_SECTOR, R2
MOVE @R2, R4
ADD 1, R4 ; R4 = increased sector
MOVE R0, R2
ADD FAT32$DEV_SECT_PER_CLUS, R2
MOVE @R2, R2
SUB 1, R2 ; we count from 0
CMP R4, R2 ; R4 > sectors per clus.?
RBRA _F32_RFDH_INCC, N ; yes: next cluster
MOVE R1, R2 ; no: write back ...
ADD FAT32$FDH_SECTOR, R2 ; ... increased sector
MOVE R4, @R2
MOVE R0, R8
MOVE R1, R2
ADD FAT32$FDH_CLUSTER_LO, R2
MOVE @R2, R9 ; LO word of cluster
MOVE R1, R2
ADD FAT32$FDH_CLUSTER_HI, R2
MOVE @R2, R10 ; HI word of cluster
MOVE R4, R11 ; sector number
RSUB FAT32$READ_SIC, 1 ; read sector in cluster
; remember the FDH responsible for filling the HW buffer
MOVE R0, R10
ADD FAT32$DEV_BUFFERED_FDH, R10
MOVE R1, @R10
RBRA _F32_RFDH_END, 1 ; done: end function
; next cluster:
; 1. reset the sector and index to 0 and write it back
; 2. retrieve the current cluster
; 3. calculate the 32bit sector offset (LBA)
; 4. retrieve FAT start address (LBA) and add offset from (3)
; 5. calculate linear address of sector and load it
; 6. retrieve and resolve the FAT32 cluster pointer, save the
; new one back to the directory handle and load the
; sector 0 within this cluster
; next cluster: reset the sector to 0 and write it back
_F32_RFDH_INCC MOVE R1, R2
ADD FAT32$FDH_SECTOR, R2
MOVE 0, @R2 ; write back sector = 0
MOVE R1, R2
ADD FAT32$FDH_INDEX, R2
MOVE 0, @R2 ; write back index = 0
; retrieve the current cluster to HI|LO = R3|R2
MOVE R1, R9
ADD FAT32$FDH_CLUSTER_HI, R9
MOVE @R9, R9
MOVE R1, R8
ADD FAT32$FDH_CLUSTER_LO, R8
MOVE @R8, R8
; calculate the 32bit sector offset:
; FAT32 stores 512 byte / 32 bit = 128 cluster pointers
; per 512 byte sector; for knowing the sector, in which we
; find the cluster pointer for the current cluster, we need
; to integer-divide it by 128; for knowing the offset
; within the sector, we look at the modulus
XOR R11, R11
MOVE 128, R10
RSUB DIVU32, 1 ; R9|R8 = sector offset
MOVE R10, R12 ; R12 = index in sector
; retrieve FAT start address (LBA) to HI|LO = R3|R2 and add
; the 32bit sector offset from HI|LO = R9|R8;
; the result is in HI|LO = R9|R8
MOVE R0, R2
ADD FAT32$DEV_FAT_LO, R2
MOVE @R2, R2
MOVE R0, R3
ADD FAT32$DEV_FAT_HI, R3
MOVE @R3, R3
ADD R2, R8
ADDC R3, R9
; ; calculate linear address of sector by multiplying the
; ; LBA address in R9|R8 by 512 and load it
; ; if the result is larger than 32bit, then we have an error
; MOVE 512, R10
; XOR R11, R11
; RSUB MULU32, 1 ; mult. result = R11..R8
; CMP R11, 0 ; result > 48bit?
; RBRA _F32_RFDH_INCC2, Z ; no: go on checking
; RBRA _F32_RFDH_INCCE, 1
;_F32_RFDH_INCC2 CMP R10, 0 ; result > 32bit?
; RBRA _F32_RFDH_INCC3, Z ; no: go on
;_F32_RFDH_INCCE MOVE FAT32$ERR_SIZE, R9 ; return size error
; RBRA _F32_RFDH_END, 1
; remember, that the current FDH was responsible for filling
; the hardware buffer
_F32_RFDH_INCC3 MOVE R0, R10
ADD FAT32$DEV_BUFFERED_FDH, R10
MOVE R1, @R10
; read 512 byte block
MOVE FAT32$DEV_BLOCK_READ, R10 ; R10 = function index
MOVE R0, R11 ; R11 = device handle
RSUB FAT32$CALL_DEV, 1
CMP R8, 0
RBRA _F32_RFDH_INCC4, Z
MOVE R8, R9
RBRA _F32_RFDH_END, 1
; retrieve and resolve the FAT32 cluster pointer, save the
; new one back to the directory handle and load the
; sector 0 within this cluster
_F32_RFDH_INCC4 MOVE R0, R8 ; R8 = device handle
MOVE R12, R9 ; R9 = index, 32bit, so
SHL 2, R9 ; multiply by 4 to get it
RSUB FAT32$READ_DW, 1 ; pointer in R11|R10
MOVE R1, R2
ADD FAT32$FDH_CLUSTER_LO, R2
MOVE R10, @R2 ; store cluster low word
MOVE R1, R2
ADD FAT32$FDH_CLUSTER_HI, R2
MOVE R11, @R2 ; store cluster high word
MOVE R0, R8 ; R8 = device handle
MOVE R10, R9 ; R9 = cluster low word
MOVE R11, R10 ; R10 = cluster high word
XOR R11, R11 ; R11 = sector 0
RSUB FAT32$READ_SIC, 1 ; load data sector; the
; FAT32$DEV_BUFFERED_FDH
; has already been
; remembered above (see
; label _F32_RFDH_INCC3)
RBRA _F32_RFDH_END, 1
; check for access beyond the sector size (means illegal hndl)
_F32_RFDH_CISS CMP R3, FAT32$SECTOR_SIZE
RBRA _F32_RFDH_DONE, !N
MOVE FAT32$ERR_CORRUPT_DH, R9
RBRA _F32_RFDH_END, 1
_F32_RFDH_DONE MOVE 0, R9
_F32_RFDH_END DECRB
MOVE R0, R8
MOVE R1, R10
MOVE R2, R11
MOVE R3, R12
DECRB
RET
;
;*****************************************************************************
;* FAT32$READ_SIC reads a sector within a cluster
;*
;* INPUT: R8: device handle
;* R9: LO word of cluster
;* R10: HI word of cluster
;* R11: sector within cluster
;* OUTPUT: R8: device handle
; R9: 0, if OK, otherweise error code
;*****************************************************************************
;
FAT32$READ_SIC INCRB
MOVE R10, R0
MOVE R11, R1
INCRB
; if sector within cluster is larger than the amount of
; clusters per sector then exit with an error message
MOVE R8, R0
ADD FAT32$DEV_SECT_PER_CLUS, R0
MOVE @R0, R0
SUB 1, R0 ; we start counting from 0
CMP R11, R0
RBRA _F32_RSIC_C1, !N
MOVE FAT32$ERR_ILLEGAL_SIC, R9
RBRA _F32_RSIC_END, 1
; all clusters numbers need to be >= 2
_F32_RSIC_C1 CMP R10, 0 ; if hi word != 0 then ...
RBRA _F32_RSIC_C2, !Z ; it is for sure >= 2
CMP R9, 1 ; if low word > 1, then
RBRA _F32_RSIC_C2, N ; it is also >= 2
MOVE FAT32$ERR_ILLEGAL_CLUS, R9
RBRA _F32_RSIC_END, 1
; lba_addr = cluster_begin_lba +
; (cluster_number - 2) * sectors_per_cluster +
; sector
_F32_RSIC_C2 MOVE R8, R0 ; save device handle
MOVE R11, R1 ; save sector
MOVE R9, R8 ; R8 = cluster LO
MOVE R10, R9 ; R9 = cluster HI
SUB 2, R8 ; cluster = cluster - 2
SUBC 0, R9
MOVE R0, R10 ; get sectors_per_cluster
ADD FAT32$DEV_SECT_PER_CLUS, R10
MOVE @R10, R10
MOVE 0, R11
RSUB MULU32, 1 ; above mentioned "*"
MOVE R0, R2 ; add cluster_begin_lba
ADD FAT32$DEV_CLUSTER_LO, R2
MOVE @R2, R2
ADD R2, R8
ADDC 0, R9
ADDC 0, R10
ADDC 0, R11
MOVE R0, R2
ADD FAT32$DEV_CLUSTER_HI, R2
MOVE @R2, R2
ADD R2, R9
ADDC 0, R10
ADDC 0, R11
ADD R1, R8 ; add sector
ADDC 0, R9
ADDC 0, R10
ADDC 0, R11
CMP 0, R11 ; too large?
RBRA _F32_RSIC_C3, Z
CMP 0, R10
RBRA _F32_RSIC_C3, Z
MOVE FAT32$ERR_SIZE, R9
RBRA _F32_RSIC_END, 1
; ; linear address = lba_addr * 512
;_F32_RSIC_C3 MOVE FAT32$SECTOR_SIZE, R10
; MOVE 0, R11
; RSUB MULU32, 1
; CMP 0, R11 ; too large?
; RBRA _F32_RSIC_C4, Z
; CMP 0, R10
; RBRA _F32_RSIC_C4, Z
; MOVE FAT32$ERR_SIZE, R9
; RBRA _F32_RSIC_END, 1
; read sector into internal buffer
_F32_RSIC_C3 MOVE FAT32$DEV_BLOCK_READ, R10
MOVE R0, R11
RSUB FAT32$CALL_DEV, 1
MOVE R8, R9
MOVE R0, R8
_F32_RSIC_END DECRB
MOVE R0, R10
MOVE R1, R11
DECRB
RET
;
;*****************************************************************************
;* FAT32$CHECKSUM computes a directory entry checksum
;*
;* Used to confirm the binding between a long filename and its corresponding
;* short filename and used to detect long filename orphans. Algorithm:
;*
;* Sum = 0;
;* for (FcbNameLen=11; FcbNameLen!=0; FcbNameLen--) {
;* // NOTE: The operation is an unsigned char rotate right
;* Sum = ((Sum & 1) ? 0x80 : 0) + (Sum >> 1) + *pFcbName++;
;*
;* INPUT: R8: pointer to the 11 bytes of a short name
;* OUTPUT: R8: 1 unsigned byte checksum (upper byte of R8 is zero)
;*****************************************************************************
;
FAT32$CHECKSUM INCRB
MOVE 11, R0 ; R0 = character count
XOR R1, R1 ; R1 = 8 bit sum
; perform an unsigned char rotate right
_F32_CHKSM_LP SHR 1, R1 ; shift right 1 into X
RBRA _F32_CHKSM_NRI, !X ; X=0: skip
OR 0x80, R1 ; X=1: rotate in a 1
; perform an unsigned char addition
_F32_CHKSM_NRI ADD @R8++, R1 ; do "+ *FcbName++"
AND 0x00FF, R1 ; unsigned char addition
; loop
SUB 1, R0
RBRA _F32_CHKSM_LP, !Z
MOVE R1, R8 ; return checksum
DECRB
RET
;
;*****************************************************************************
;* FAT32$READ_B reads a byte from the current sector buffer
;*
;* INPUT: R8: pointer to mount data structure (device handle)
;* R9: address (0 .. 511)
;* OUTPUT: R10: the byte that was read
;*****************************************************************************
;
FAT32$READ_B INCRB
MOVE R8, R0
MOVE R11, R1
MOVE R12, R2
MOVE R8, R11 ; mount data structure
MOVE R9, R8 ; read address
MOVE FAT32$DEV_BYTE_READ, R10
RSUB FAT32$CALL_DEV, 1
MOVE R8, R10
MOVE R0, R8
MOVE R1, R11
MOVE R2, R12
DECRB
RET
;
;*****************************************************************************
;* FAT32$READ_W reads a word from the current sector buffer
;*
;* Assumes that the buffer is stored in little endian (as this is the case
;* for MBR and FAT32 data structures)
;*
;* INPUT: R8: pointer to mount data structure (device handle)
;* R9: address (0 .. 511)
;* OUTPUT: R10: the word that was read
;*****************************************************************************
;
FAT32$READ_W INCRB
MOVE R8, R0
MOVE R9, R1
RSUB FAT32$READ_B, 1 ; read low byte ...
MOVE R10, R2 ; ... and remember it
ADD 1, R9 ; read high byte ...
RSUB FAT32$READ_B, 1
MOVE R10, R3 ; ... and remember it
SWAP R3, R10 ; R3 lo = high byte of R10
OR R2, R10 ; R2 lo = low byte of R10
MOVE R0, R8
MOVE R1, R9
DECRB
RET
;
;*****************************************************************************
;* FAT32$READ_DW reads a double word from the current sector buffer
;*
;* Assumes that the buffer is stored in little endian (as this is the case
;* for MBR and FAT32 data structures)
;*
;* INPUT: R8: pointer to mount data structure (device handle)
;* R9: address (0 .. 511)
;* OUTPUT: R10: the low word that was read
;* R11: the high word that was read
;*****************************************************************************
;
FAT32$READ_DW INCRB
MOVE R9, R0
RSUB FAT32$READ_W, 1
MOVE R10, R1
ADD 2, R9
RSUB FAT32$READ_W, 1
MOVE R10, R11
MOVE R1, R10
MOVE R0, R9
DECRB
RET
;=============================================================================
;=============================================================================
;
; POTENTIAL EXTENSIONS OF OTHER MONITOR LIBRARIES START HERE
;
;=============================================================================
;=============================================================================
;
;*****************************************************************************
;* MULU32 multiplies two 32bit unsigned values and returns a 64bit unsigned
;*
;* INPUT: R8/R9 = LO/HI of unsigned multiplicant 1
;* R10/R11 = LO/HI of unsigned multiplicant 2
;* OUTPUT: R11/R10/R9/R8 = HI .. LO of 64bit result
;*****************************************************************************
;
MULU32 INCRB ; registers R3..R0 = result ..
INCRB ; .. therefore two INCRBs
; save arguments as in R1|R0 * R3|R2
MOVE R8, R0
MOVE R9, R1
MOVE R10, R2
MOVE R11, R3
; algorithm:
; R1R0
; x R3R2
; ----------
; R2R0
; + R2R1
; + R3R0
; + R3R1
; ----------
MOVE R0, R8 ; R2 * R0
MOVE R2, R9
SYSCALL(mulu, 1) ; result in R11|R10
DECRB
MOVE R10, R0
MOVE R11, R1
XOR R2, R2
XOR R3, R3
INCRB
MOVE R1, R8 ; R2 * R1
MOVE R2, R9
SYSCALL(mulu, 1)
DECRB
ADD R10, R1
ADDC R11, R2
ADDC 0, R3
INCRB
MOVE R0, R8 ; R3 * R0
MOVE R3, R9
SYSCALL(mulu, 1)
DECRB
ADD R10, R1
ADDC R11, R2
ADDC 0, R3
INCRB
MOVE R1, R8 ; R3 * R1
MOVE R3, R9
SYSCALL(mulu, 1)
DECRB
ADD R10, R2
ADDC R11, R3
MOVE R3, R11 ; store result (return values)
MOVE R2, R10
MOVE R1, R9
MOVE R0, R8
DECRB
RET
;
;*****************************************************************************
;* DIVU32 divides 32bit dividend by 32bit divisor and returns
;* a 32bit quotient and a 32bit modulo
;* warning: no division by zero warning; instead, the function returns
;* zero as result and as modulo
;*
;* INPUT: R8/R9 = LO|HI of unsigned dividend
;* R10/R11 = LO|HI of unsigned divisor
;* OUTPUT: R8/R9 = LO|HI of unsigned quotient
;* R10/R11 = LO|HI of unsigned modulo
;*****************************************************************************
;
DIVU32 INCRB
; perform the division by using the following algorithm, where
; N = dividend = HI|LO = R9|R8
; D = divisor = HI|LO = R11|R10
; Q = quotient = HI|LO = R1|R0
; R = remainder (modulo) = HI|LO = R3|R2
;
; Q := 0 quotient and remainder = 0
; R := 0
; for i = n−1...0 do where n is number of bits in N
; R := R << 1 left-shift R by 1 bit
; R(0) := N(i) set the least-significant bit
; of R equal to bit i of the divisor
; if R >= D then
; R := R − D
; Q(i) := 1
; end
; end
XOR R0, R0 ; HI|LO = R1|R0 = quotient
XOR R1, R1
XOR R2, R2 ; HI|LO = R3|R2 = reminder
XOR R3, R3
; division by zero
; as we have no interrupts and no additional error flag,
; we return zero on a division by zero
CMP R11, R0
RBRA _DIVU32_START, !Z
CMP R10, R0
RBRA _DIVU32_START, !Z
RBRA _DIVU32_END, 1
_DIVU32_START MOVE 31, R4 ; R4 = bit counter: 31 .. 0
; R := R << 1: 32bit shift-left of R
_DIVU32_NEXTBIT AND 0xFFFD, SR ; clear X (shift in '0')
SHL 1, R2 ; MSB of lo word shifts to C
RBRA _DIVU32_SHL0, !C ; C=0 => X=0
OR 0x0002, SR ; C=1 => X=1
SHL 1, R3 ; hi word (shifts in X=1)
RBRA _DIVU32_RNI, 1
_DIVU32_SHL0 AND 0xFFFD, SR ; C=0 => X=0
SHL 1, R3 ; hi word (shifts in X=0)
; R(0) := N(i)
_DIVU32_RNI MOVE R4, R6
CMP R4, 15 ; R4 <= 15?
RBRA _DIVU32_RNIL, !N ; yes: consider low word of R
MOVE R9, R5 ; high word of N
SUB 16, R6 ; correct index b/c of hi word
RBRA _DIVU32_RNIH, 1
_DIVU32_RNIL MOVE R8, R5 ; low word of N
_DIVU32_RNIH AND 0xFFFB, SR ; clear C
SHR R6, R5 ; extract bit by SHR to the ..
AND 1, R5 ; ..LSB pos. and and-ing 1
AND 0xFFFE, R2 ; clear target bit and ...
OR R5, R2 ; ... set it again, if needed
; if R >= D then
; done by doing 32bit R - D and checking:
; if MSB = 1, then R < D, else R >= D
; hint: MSB in this case is the 33th bit, i.e. bit #32 = carry
MOVE R2, R5 ; R5 = low word of R
MOVE R3, R6 ; R6 = high word of R
SUB R10, R5 ; R6|R5 = 32bit (R := R - D)
SUBC R11, R6
RBRA _DIVU32_ITERATE, C ; bit #32 is "negative"
; when reaching this code, R is >= D
; if R >= D then
; R := R − D
; Q(i) := 1
; end
MOVE R6, R3 ; R = R6|R5 = (R := R - D)
MOVE R5, R2
MOVE 1, R7 ; Q(i) := 1 by shifting a "1"
CMP R4, 15 ; R4 <= 15?
RBRA _DIVU32_QL, !N ; yes: consider low word of Q
MOVE R4, R6 ; R6 := i
SUB 16, R6 ; adjust for high word
AND 0xFFFD, SR ; clear X
SHL R6, R7 ; move "1" to the right place
OR R7, R1 ; R1:= hi Q(i) := 1
RBRA _DIVU32_ITERATE, 1
_DIVU32_QL AND 0xFFFD, SR ; clear X
SHL R4, R7 ; move "1" to the right place
OR R7, R0 ; R0 := low Q(i) := 1
; for i = n−1...0 do (i.e. also one loop for the case i=0)
_DIVU32_ITERATE SUB 1, R4
RBRA _DIVU32_NEXTBIT, !N ; !N includes a round for i=0
; return results
_DIVU32_END MOVE R1, R9 ; hi Q
MOVE R0, R8 ; lo Q
MOVE R3, R11 ; hi R
MOVE R2, R10 ; lo R
DECRB
RET
;
;*****************************************************************************
;* TO_LOWER converts the character in R8 to lower case
;* (already added to the monitor, but not synthesized yet as of 16/7/3)
;* (so this is TEMP; replace by SYSCALL, soon)
;*****************************************************************************
;
TO_LOWER INCRB
CMP R8, '@' ; Is it "@" or less than that?
RBRA _TO_LOWER_EXIT, !N ; Yes: nothing to do
CMP R8, 'Z' ; Is it greater than 'Z'
RBRA _TO_LOWER_EXIT, N ; Yes: nothing to do
ADD 0x0020, R8 ; Perform the conversion
_TO_LOWER_EXIT DECRB
RET
;
;*****************************************************************************
;* SPLIT splits a string into substrings using a delimiter char
;*
;* Returns the substrings on the stack, i.e. after being done, you need to
;* add the amount of words returned in R9 to the stack pointer to clean
;* it up again and not leaving "memory leaks".
;*
;* The memory layout of the returned area is:
;* <size of string incl. zero terminator><string><zero terminator>
;*
;* The strings are returned in positive order, i.e. you just need to add
;* the length of the previous string to the returned string pointer
;* (i.e. stack pointer) to jump to the next substring from left to right.
;*
;* INPUT: R8: pointer to zero terminated string
;* R9: delimiter char
;* OUTPUT: SP: stack pointer pointer to the first string
;* R8: amount of strings
;* R9: amount of words to add to the stack pointer to restore it
;*****************************************************************************
;
SPLIT INCRB
MOVE @SP++, R0 ; save return address and
; delete it by adding 1
; find the end of the string, R1 will point to it
MOVE 1, R2
MOVE R8, R1
_SPLIT_FE CMP @R1, 0
RBRA _SPLIT_FE2, Z
ADD R2, R1
RBRA _SPLIT_FE, 1
_SPLIT_FE2 MOVE R1, R2 ; R2 = end of current substr
XOR R6, R6 ; R6 = amount of strings
XOR R7, R7 ; R7 = amount of words for R9
; skip empty string
CMP R8, R1
RBRA _SPLIT_ES_END, Z
; find the first occurrence of the delimiter
_SPLIT_FD CMP @--R1, R9 ; check for delimiter, mv left
RBRA _SPLIT_SS, Z ; yes, delimiter found
CMP R1, R8 ; beginning of string reached?
RBRA _SPLIT_SS, Z
RBRA _SPLIT_FD, 1
; copy substring on the stack, if it is at least one
; non-delimiter character
_SPLIT_SS MOVE R2, R3
SUB R1, R3 ; length of substring w/o zero
CMP R3, 1 ; only one character?
RBRA _SPLIT_SSB, !Z ; no: go on
CMP @R1, R9 ; only one char and char=delim
RBRA _SPLIT_SS_SKP, Z ; yes: skip
_SPLIT_SSB ADD 1, R6 ; one more string
SUB R3, SP ; reserve memory on the stack
SUB 2, SP ; size word & zero terminator
ADD R3, R7 ; adjust amount of words ..
ADD 2, R7 ; .. equally to stack usage
CMP @R1, R9 ; first char delimiter?
RBRA _SPLIT_SS_BGN, !Z ; no: go on
ADD 1, SP ; yes: adjust stack usage ..
SUB 1, R7 ; .. and word counter ..
SUB 1, R3 ; .. and reduce length ..
ADD 1, R1 ; .. and change start
_SPLIT_SS_BGN MOVE R1, R4 ; R4 = cur. char of substring
MOVE SP, R5 ; R5 = target memory of char
MOVE R3, @R5 ; save size w/o zero term.
ADD 1, @R5++ ; adjust for zero term.
_SPLIT_SS_CPY MOVE @R4++, @R5++ ; copy char
SUB 1, R3 ; R3 = amount to be copied
RBRA _SPLIT_SS_CPY, !Z
MOVE 0, @R5 ; add zero terminator
_SPLIT_SS_SKP MOVE R1, R2 ; current index = new end
CMP R1, R8 ; beginning of string reached?
RBRA _SPLIT_FD, !Z
_SPLIT_ES_END MOVE R6, R8 ; return amount of strings
MOVE R7, R9 ; return amount of bytes
MOVE R0, @--SP ; put return address on stack
DECRB
RET
;
;*****************************************************************************
;* H2D_ASCII converts a 32bit value to a decimal representation in ASCII;
;* leading zeros are replaced by spaces (ASCII 0x20); zero terminator is added
;*
;* INPUT: R8/R9 = LO/HI of the 32bit value
;* R10 = pointer to a free memory area that is 11 words long
;* OUTPUT: R10 = the function fills the given memory space with the
;* decimal representation and adds a zero terminator
;* this includes leading white spaces
;* R11 = pointer to string without leading white spaces
;* R12 = amount of digits/characters that the actual number has,
;* without the leading spaces
;*****************************************************************************
;
H2D_ASCII INCRB
MOVE R8, R0 ; save original values
MOVE R9, R1
MOVE R10, R2
MOVE R10, R3 ; R3: working pointer
XOR R4, R4 ; R4: digit counter
; add zero terminator
ADD 10, R3
MOVE 0, @R3
; extract decimals by repeatedly dividing the 32bit value
; by 10; the modulus is the decimal that is converted to
; ASCII by adding the ASCII code of zero which is 0x0030
XOR R11, R11 ; high word = 0
_H2DASCII_ML MOVE 10, R10 ; divide by 10
RSUB DIVU32, 1 ; perform division
ADD 0x0030, R10 ; R10 = digit => ASCII conv.
MOVE R10, @--R3 ; store digit
ADD 1, R4 ; increase digit counter
CMP R8, 0 ; quotient = 0? (are we done?)
RBRA _H2DASCII_TS, Z ; yes: add trailing spaces
RBRA _H2DASCII_ML, 1 ; next digit, R8/R9 has result
_H2DASCII_TS CMP R3, R2 ; working pntr = memory start
RBRA _H2DASCII_DONE, Z ; yes: then done
MOVE 0x0020, @--R3 ; no: add trailing space
RBRA _H2DASCII_TS, 1 ; next digit
_H2DASCII_DONE MOVE R0, R8 ; restore original values
MOVE R1, R9
MOVE R2, R10
MOVE R10, R11 ; return pointer to string ..
MOVE 10, R7 ; .. without white spaces
SUB R4, R7
ADD R7, R11
MOVE R4, R12 ; return digit counter
DECRB
RET
;=============================================================================
;=============================================================================
;
; POTENTIAL EXTENSIONS OF THE MONITOR END HERE
;
;=============================================================================
STRBUF .BLOCK 1
bb84e3173This file is pinned to the selected release. Download the original to open it in your design tool.