//------------------------------------------------------------------------ // FAT16 Library Low Level // // Author: Mattias Hansson // Target: Commodore 64 with MMC64 interface // (possible future conversions for other platforms possible) // // Purpose: To provide lowlevel access routines for the FAT16 Library // // Routines: // lib_fat16_int_read_cluster_sector (INTERNAL) // // // //------------------------------------------------------------------------ #IFNDEF __LIB_FAT16LOW #DEFINE __LIB_FAT16LOW = 1 GOTO lib_fat16low_skip //------------------------------------------------------------------------ // FAT16 constants //------------------------------------------------------------------------ WORD CONST LIB_FAT16_CLUSTER_FREE = $0000 WORD CONST LIB_FAT16_CLUSTER_END = $fff0 //this value or above = clusterchain ended BYTE CONST LIB_FAT16_DIRENTRY_SIZE = $20 BYTE CONST LIB_FAT16_DELETED_FILE = $e5 BYTE CONST LIB_FAT16_END_OF_DIR = $00 WORD CONST LIB_FAT16_SECTOR_SIZE = 512 //******Do NOT EVER access this var as a regular variable*********** //special: this is NOT a regular variable for usage!!!!!!!!!!!!!!!!! //The "Address" defines how many files may be open simultainously!!! //If you want to change the number of open files, change the!!!!!!!! //address to the number you of files you want open!!!!!!!!!!!!!!!!!! BYTE lib_fat16_max_open_files @ __LIB_FAT16_MAX_OPEN_FILES //******Do NOT EVER access this var as a regular variable*********** BYTE CONST LIB_FAT16_FILEMODE_READ = 0 BYTE CONST LIB_FAT16_FILEMODE_WRITE = 1 //This filemode is special as it's only valid for fclose() //This file gets set when blockwriting another size then $512 //as further writes would not be alligned with the sectors. BYTE CONST LIB_FAT16_FILEMODE_WRITE_ENDED = 2 //BYTE CONST LIB_FAT16_FILEMODE_READWRITE = 2 //future expansion #IFDEF __LIB_FAT16_WRITESUPPORT //This constant controls how many consecutive clusters will be controlled //for beeing free when mounting a partition for read/write access. //Generally speaking this is good when having an unfragmented disk/card //with just a little data on it, as i shortens the mount time. //However if you'll be writing much data (like several megabytes) you'll //probably want to set this to $ffff (no limit) since this will not add //any performance penalty (for searching new free clusters) when you've //reached the limit. #IFDEF __LIB_MMC64_OPTIMIZE_SPEED WORD CONST LIB_FAT16_WRITE_CLUSTERS_MAX = $400 #IFEND #IFNDEF __LIB_MMC64_OPTIMIZE_SPEED //Explanation: if we don't cache the reads, every single cluster //we search (in fat) will force the lib to re-read the sector again //which will take painfully much time #PRINT __LIB_MMC64_OPTIMIZE_SPEED combined with __LIB_FAT16_WRITESUPPORT is catastrophic performance-wise. WORD CONST LIB_FAT16_WRITE_CLUSTERS_MAX = $10 #IFEND #IFEND //------------------------------------------------------------------------ // FAT16 common variables //------------------------------------------------------------------------ //Info for developers: the lib_fat16_buffer_ptr must point out an 512 byte area //You can choose to do so by a constant or variable. //Default is a variable that points out an area reserved inside this library //called "lib_fat16_fatbuffer". If you change this to a constant, remark //the "lib_fat16_fatbuffer" area to save the unused filler. //WORD CONST lib_fat16_buffer_ptr = $c800 //change this for your particular usage // //(size needed is "one sector" = 512 bytes ) WORD lib_fat16_buffer_ptr BYTE lib_fat16_sectors_per_cluster // needed for cluster reading routine WORD lib_fat16_reserved_sectors // offset from FAT16 boot record to FATs (file allocation tables) BYTE lib_fat16_nr_of_fats // self*sectors_per_fat = offset from FATs to the root directory WORD lib_fat16_sectors_per_fat // (see above) WORD lib_fat16_rootdir_entries // Max file-entries in the ROOT directory // ( self * size of directory entry (32) ) / 512 = // offset from ROOT directory to DATA area (where clusters begin. // N.B. First cluster here is #2 (!)) WORD lib_fat16_rootdir_sectors // calculated size of rootdir measured in sectors. #IFDEF __LIB_FAT16_WRITESUPPORT WORD lib_fat16_total_clusters = 0 // ( sectors_per_fat * bytes_per_sector ($200) ) / 2 == ( sectors_per_fat * $100 ). WORD lib_fat16_first_write_cluster = $ffff //these two are used internally by lib_fat16_int_find_unused_clusters and WORD lib_fat16_last_write_cluster = $0000 //lib_fat16_fwriteblock to find new areas for writing bit by bit (as needed) WORD lib_fat16_total_sectors_lo WORD lib_fat16_total_sectors_hi #IFEND //Temp ZP-variables for usage in the lib. WORD lib_fat16_tempw @ $3f WORD lib_fat16_tempw2 @ $4b BYTE lib_fat16_tempb @ $41 //------------------------------------------------------------------------ // 512 byte internal buffer for FAT16 operations. //------------------------------------------------------------------------ LABEL lib_fat16_fatbuffer ASM !fill 512 ENDASM //------------------------------------------------------------------------ // lib_fat16_int_read_cluster_sector (INTERNAL) // // Purpose: // 1. To read out a specific sector in a specific cluster // or // 2. To read out a sector in the root-dir // // Parameters: // lib_fat16_int_rc_cluster - cluster to read. special: 0 = root-dir // lib_fat16_int_rc_sector - sector # inside the cluster (0..lib_fat16_sectors_per_cluster-1) // special: if in root-dir (0..lib_fat16_rootdir_sectors-1) // lib_fat16_int_rc_buffer_ptr - pointer, where to put the read sector in memory // lib_fat16_int_rc_status - 0 = OK. !0 = error. // // Preparation: lib_fat16_mount_partition // // Usage of lib temp variables: // lib_fat16_tempw2 // lib_fat16_tempb //------------------------------------------------------------------------ WORD lib_fat16_int_rc_cluster WORD lib_fat16_int_rc_sector WORD lib_fat16_int_rc_buffer_ptr BYTE lib_fat16_int_rc_status FUNC lib_fat16_int_read_cluster_sector ( lib_fat16_int_rc_cluster lib_fat16_int_rc_sector lib_fat16_int_rc_buffer_ptr out:lib_fat16_int_rc_status ) WORD first_pos // in bytes WORD read_count LET first_pos = 0 LET read_count = LIB_FAT16_SECTOR_SIZE FUNC lib_fat16_int_read_cluster_sector_part ( lib_fat16_int_rc_cluster lib_fat16_int_rc_sector lib_fat16_int_rc_buffer_ptr first_pos read_count out:lib_fat16_int_rc_status ) BYTE spc_count WORD csect_offset_lo BYTE csect_offset_hi BYTE callstat //status for internal calls LET lib_fat16_int_rc_status = 1 //indicate an error at first (we SUBEND out if things go wrong) IF lib_fat16_int_rc_cluster == 0 //the root directory IF lib_fat16_int_rc_sector >= lib_fat16_rootdir_sectors SUBEND // caller tried to make us read past the root-dir region. probably an internal error. ENDIF //Now we must add the start sector for the rootdir with the sector requested inside it, to get the right one ASM clc lda lib_fat16_rootdir_sector_lo adc lib_fat16_int_rc_sector sta lib_fat16_tempw2 lda lib_fat16_rootdir_sector_lo+1 adc lib_fat16_int_rc_sector+1 sta lib_fat16_tempw2+1 lda lib_fat16_rootdir_sector_hi adc #0 sta lib_fat16_tempb ENDASM CALL lib_mmc64_readsector ( lib_fat16_tempw2 lib_fat16_tempb lib_fat16_int_rc_buffer_ptr callstat ) IF callstat != 0 SUBEND ENDIF GOTO lib_fat16_int_rc_exit_success ENDIF //Else this is a real cluster in the dataarea that's about to be read IF lib_fat16_int_rc_sector >= lib_fat16_sectors_per_cluster SUBEND // caller passed a sector offset beyond the end of the cluster. probably an internal error ENDIF //Adjust for data-area starting with cluster #2 SUBT lib_fat16_int_rc_cluster - 2 -> lib_fat16_int_rc_cluster //Now we must multiply the clusternr with "nr of sectors per cluster" to //get a valid sector-offset into the data-area LET csect_offset_lo = lib_fat16_int_rc_cluster LET csect_offset_hi = 0 LET spc_count = lib_fat16_sectors_per_cluster //First bit unimportant (1 sector per cluster and we're done here!) ASM lsr |spc_count| ENDASM WHILE spc_count ASM clc rol |csect_offset_lo| rol |csect_offset_lo|+1 rol |csect_offset_hi| lsr |spc_count| ENDASM WEND // "real sector to access" = "start of data area" + "cluster sector offset" + lib_fat16_int_rc_sector (sector # in cluster) //first we do "start of data area" + "cluster sector offset" ASM clc lda lib_fat16_data_sector_lo adc |csect_offset_lo| sta lib_fat16_tempw2 lda lib_fat16_data_sector_lo+1 adc |csect_offset_lo|+1 sta lib_fat16_tempw2+1 lda lib_fat16_data_sector_hi adc |csect_offset_hi| sta lib_fat16_tempb ENDASM //now we add the lib_fat16_int_rc_sector ASM clc lda lib_fat16_tempw2 adc |lib_fat16_int_rc_sector| sta lib_fat16_tempw2 lda lib_fat16_tempw2+1 adc #0 ; we know that "sectors per cluster" cannot exceed 128. sta lib_fat16_tempw2+1 lda lib_fat16_tempb adc #0 sta lib_fat16_tempb ENDASM //Mostly we read whole sectors. However if the file ends in (( size % 0x200 )!=0) the file ends someplace inside //a sector, and we must read the last sector partly. CALL lib_mmc64_readsector_part ( lib_fat16_tempw2 lib_fat16_tempb lib_fat16_int_rc_buffer_ptr first_pos read_count callstat ) IF callstat != 0 SUBEND ENDIF LABEL lib_fat16_int_rc_exit_success LET lib_fat16_int_rc_status = 0 FEND //------------------------------------------------------------------------ // lib_fat16_int_write_cluster_sector (INTERNAL) // // Purpose: // 1. To write a specific sector in a specific cluster // or // 2. To write a sector in the root-dir // // Parameters: // lib_fat16_int_wc_cluster - cluster to write. special: 0 = root-dir // lib_fat16_int_wc_sector - sector # inside the cluster (0..lib_fat16_sectors_per_cluster-1) // special: if in root-dir (0..lib_fat16_rootdir_sectors-1) // lib_fat16_int_wc_buffer_ptr - pointer, sector-data in memory to write // lib_fat16_int_wc_status - 0 = OK. !0 = error. // // Preparation: lib_fat16_mount_partition // // Usage of lib temp variables: // lib_fat16_tempw2 // lib_fat16_tempb //------------------------------------------------------------------------ #IFDEF __LIB_FAT16_WRITESUPPORT WORD lib_fat16_int_wc_cluster WORD lib_fat16_int_wc_sector WORD lib_fat16_int_wc_buffer_ptr BYTE lib_fat16_int_wc_status FUNC lib_fat16_int_write_cluster_sector ( lib_fat16_int_wc_cluster lib_fat16_int_wc_sector lib_fat16_int_wc_buffer_ptr out:lib_fat16_int_wc_status ) WORD first_pos // in bytes WORD write_count LET first_pos = 0 LET write_count = LIB_FAT16_SECTOR_SIZE FUNC lib_fat16_int_write_cluster_sector_part ( lib_fat16_int_wc_cluster lib_fat16_int_wc_sector lib_fat16_int_wc_buffer_ptr first_pos write_count out:lib_fat16_int_wc_status ) BYTE spc_count WORD csect_offset_lo BYTE csect_offset_hi BYTE callstat //status for internal calls LET lib_fat16_int_wc_status = 1 //indicate an error at first (we SUBEND out if things go wrong) IF lib_fat16_int_wc_cluster == 0 //the root directory IF lib_fat16_int_wc_sector >= lib_fat16_rootdir_sectors SUBEND // caller tried to make us read past the root-dir region. probably an internal error. ENDIF //Now we must add the start sector for the rootdir with the sector requested inside it, to get the right one ASM clc lda lib_fat16_rootdir_sector_lo adc lib_fat16_int_wc_sector sta lib_fat16_tempw2 lda lib_fat16_rootdir_sector_lo+1 adc lib_fat16_int_wc_sector+1 sta lib_fat16_tempw2+1 lda lib_fat16_rootdir_sector_hi adc #0 sta lib_fat16_tempb ENDASM CALL lib_mmc64_writesector ( lib_fat16_tempw2 lib_fat16_tempb lib_fat16_int_wc_buffer_ptr callstat ) IF callstat != 0 SUBEND ENDIF GOTO lib_fat16_int_wc_exit_success ENDIF //Else this is a real cluster in the dataarea that's about to be read IF lib_fat16_int_wc_sector >= lib_fat16_sectors_per_cluster SUBEND // caller passed a sector offset beyond the end of the cluster. probably an internal error ENDIF //Adjust for data-area starting with cluster #2 SUBT lib_fat16_int_wc_cluster - 2 -> lib_fat16_int_wc_cluster //Now we must multiply the clusternr with "nr of sectors per cluster" to //get a valid sector-offset into the data-area LET csect_offset_lo = lib_fat16_int_wc_cluster LET csect_offset_hi = 0 LET spc_count = lib_fat16_sectors_per_cluster //First bit unimportant (1 sector per cluster and we're done here!) ASM lsr |spc_count| ENDASM WHILE spc_count ASM clc rol |csect_offset_lo| rol |csect_offset_lo|+1 rol |csect_offset_hi| lsr |spc_count| ENDASM WEND // "real sector to access" = "start of data area" + "cluster sector offset" + lib_fat16_int_wc_sector (sector # in cluster) //first we do "start of data area" + "cluster sector offset" ASM clc lda lib_fat16_data_sector_lo adc |csect_offset_lo| sta lib_fat16_tempw2 lda lib_fat16_data_sector_lo+1 adc |csect_offset_lo|+1 sta lib_fat16_tempw2+1 lda lib_fat16_data_sector_hi adc |csect_offset_hi| sta lib_fat16_tempb ENDASM //now we add the lib_fat16_int_wc_sector ASM clc lda lib_fat16_tempw2 adc |lib_fat16_int_wc_sector| sta lib_fat16_tempw2 lda lib_fat16_tempw2+1 adc #0 ; we know that "sectors per cluster" cannot exceed 128. sta lib_fat16_tempw2+1 lda lib_fat16_tempb adc #0 sta lib_fat16_tempb ENDASM //Mostly we read whole sectors. However if the file ends in (( size % 0x200 )!=0) the file ends someplace inside //a sector, and we must read the last sector partly. CALL lib_mmc64_writesector_part ( lib_fat16_tempw2 lib_fat16_tempb lib_fat16_int_wc_buffer_ptr first_pos write_count callstat ) IF callstat != 0 SUBEND ENDIF LABEL lib_fat16_int_wc_exit_success LET lib_fat16_int_wc_status = 0 FEND #IFEND //------------------------------------------------------------------------ // lib_fat16_int_get_cluster_link (INTERNAL) // // Purpose: // Return the next cluster nr for a given cluster. // In the FAT the position for the current cluster holds the link // to the next cluster in the "chain". // special values are: $0000 unallocated, $ffef-$ffff end-of-cluster-chain // // Parameters: // lib_fat16_int_nc_cluster_nr - we send in our current cluster number // and we get the next returned in the // same param // lib_fat16_int_nc_status - the status operation. 0 = success // !0 = failure (probably internal) // // Preparation: lib_fat16_dirnext (caller) // (unfinished) lib_fat16_fopen // // Usage of lib temp variables: // lib_fat16_tempw2 // lib_fat16_tempb // // // lib_fat16_int_set_cluster_link (INTERNAL) // // Purpose: to update the two FAT's with a new value for the cluster-link // // Additional parameter: // lib_fat16_int_nc_target_cluster_nr = the new cluster-link to write // // Sorry for the clutter, but this way saves a whole lot of code to // support write-access //Mattias // // A note on caching: Since a fat-sector is read, updated and written back // at once we don't need to invalidate the "caching" of the FAT-buffer. // (a later cached read will still be valid) //------------------------------------------------------------------------ WORD lib_fat16_int_nc_cluster_nr BYTE lib_fat16_int_nc_status #IFDEF __LIB_FAT16_WRITESUPPORT BYTE lib_fat16_int_nc_writeflag WORD lib_fat16_int_nc_target_cluster_nr FUNC lib_fat16_int_set_cluster_link ( lib_fat16_int_nc_cluster_nr lib_fat16_int_nc_target_cluster_nr out:lib_fat16_int_nc_status ) LET lib_fat16_int_nc_writeflag = 1 #IFEND FUNC lib_fat16_int_get_cluster_link ( io:lib_fat16_int_nc_cluster_nr out:lib_fat16_int_nc_status ) BYTE sect_offset BYTE callstat WORD sector_lo BYTE sector_hi //MHTAG 20050927 - this internal variable was replaced internally for lib_fat16_tempw2 (ZP-vars == better code) //WORD lib_fat16_int_nc_link_offset_bytes LET lib_fat16_int_nc_status = 1 //Clusters in the data-area are numbered from 2 (i.e. 2 = offset 0 ) IF lib_fat16_int_nc_cluster_nr < 2 SUBEND ENDIF //Values from $fff0 are "bad", reserved or cluster-chain end markers. (endmark>=$fff8) //We don't care here, since we cannot follow longer anyhow. IF lib_fat16_int_nc_cluster_nr >= LIB_FAT16_CLUSTER_END SUBEND ENDIF // exp for the sector offset on disk for requested cluster entry: // "first FAT sector" + ( (cluster_nr * "sizeof cluster entry (2 bytes in FAT16 )") / 512 ) // simplified = "first FAT sector" + ( cluster_nr / 256 ) i.e. the high byte of "cluster nr" // div 256 = shift left 8 bits, so we simply take the highbyte and move it to the lowbyte (see below) ASM lda lib_fat16_int_nc_cluster_nr+1 sta |sect_offset| ENDASM //and now we add the "first FAT sector" ASM clc lda lib_fat16_fat1_sector_lo adc |sect_offset| sta lib_fat16_tempw2 lda lib_fat16_fat1_sector_lo+1 adc #0 sta lib_fat16_tempw2+1 lda lib_fat16_fat1_sector_hi adc #0 sta lib_fat16_tempb ENDASM LET sector_lo = lib_fat16_tempw2 LET sector_hi = lib_fat16_tempb //read up the sector CALL lib_mmc64_readsector ( sector_lo sector_hi lib_fat16_buffer_ptr callstat ) IF callstat != 0 SUBEND //pass the read error on to the caller. ENDIF //calculate the position of the link //Following the logic above the offset the the position of the link in the sector must be in the low-byte //However since every entry is 2 bytes long, we must multiply the lowbyte with 2 to get the offset in bytes // //MHTAG 20050927 - all usage of lib_fat16_tempw2 below in this func is replacement for lib_fat16_int_nc_link_offset_bytes ASM clc lda lib_fat16_int_nc_cluster_nr rol sta lib_fat16_tempw2 lda #0 rol sta lib_fat16_tempw2+1 ENDASM ADD lib_fat16_tempw2 + lib_fat16_buffer_ptr -> lib_fat16_tempw2 #IFDEF __LIB_FAT16_WRITESUPPORT //if readaccess - skit this section IF lib_fat16_int_nc_writeflag == 1 //this section updates the cluster link value and writes the fat-sector back to disk PUTASWORD@ lib_fat16_tempw2 VALUE lib_fat16_int_nc_target_cluster_nr CALL lib_mmc64_writesector ( sector_lo sector_hi lib_fat16_buffer_ptr callstat ) IF callstat != 0 SUBEND //pass the read error on to the caller. ENDIF //now we must update the second FAT. //change here to support more than 2 FATs //reason for decision to support only 2 = VERY standard. Even linux vfat driver relies on two FATs //24-bit addition of the size of one fat to get right offset ASM clc lda |sector_lo| adc lib_fat16_sectors_per_fat sta |sector_lo| lda |sector_lo|+1 adc lib_fat16_sectors_per_fat+1 sta |sector_lo|+1 lda |sector_hi| adc #0 sta |sector_hi| ENDASM //and finally the actual write. CALL lib_mmc64_writesector ( sector_lo sector_hi lib_fat16_buffer_ptr callstat ) IF callstat != 0 SUBEND //pass the read error on to the caller. ENDIF GOTO lib_fat16_int_nc_skipread ENDIF #IFEND //return it GETASWORD@ lib_fat16_tempw2 -> lib_fat16_int_nc_cluster_nr #IFDEF __LIB_FAT16_WRITESUPPORT LABEL lib_fat16_int_nc_skipread LET lib_fat16_int_nc_writeflag = 0 //reset for next access of this function #IFEND LET lib_fat16_int_nc_status = 0 FEND //------------------------------------------------------------------------ // lib_fat16_int_search_free_clusters // // Purpose: To find clusters on the mounted drive that are free to allocate // for new filedata. // // Usage: When mounting a partition for R/W access this routine is called // to find unused clusters, in the event that a write is issued. The // routine searches for a "cluster hole" of max LIB_FAT16_WRITE_CLUSTERS_MAX // continous clusters (to reduce search time). It updates the variables // lib_fat16_first_write_cluster and lib_fat16_last_write_cluster with // the limits of the found "cluster hole" (range of unallocated clusters) // // If writing has completely filled previously discovered "cluster hole" // the write routines may call this routine again to try to find more // unclaimed disc space to fill. // // Method: // Space for writing is searched from the end of the FAT (last clusters) // and backwards until we find free clusters. When we find a free cluster // we set the "lib_fat16_last_write_cluster" marker and continue backwards // for a while (until we reach a count or until we hit data again. This is // where we set the "lib_fat16_first_write_cluster" marker. // After this operation we know about an area between the two markers that // consists of free clusters, ready to use for writing file-data //------------------------------------------------------------------------ #IFDEF __LIB_FAT16_WRITESUPPORT BYTE lib_fat16_int_fu_status FUNC lib_fat16_int_search_free_clusters ( out:lib_fat16_int_fu_status ) WORD active_cluster WORD active_link WORD cluster_search_max BYTE callstat LET lib_fat16_int_fu_status = 1 //default = error exit //----------------------------------------------------- //TASK1 - find out where to start the search LET active_cluster = lib_fat16_first_write_cluster IF active_cluster == $ffff //if free area is uninitialized yet LET active_cluster = lib_fat16_total_clusters //start the search at the end of the fat ENDIF //----------------------------------------------------- //TASK2 - rewind until we reach: // a) free clusters // b) start of fat WHILE 1 LET active_link = active_cluster CALL lib_fat16_int_get_cluster_link ( active_link callstat ) IF callstat != 0 SUBEND //internal error ENDIF IF active_link == 0 BREAK ENDIF DEC active_cluster WEND IF active_cluster < 2 //disk is full. Unable to find a single free cluster! SUBEND ENDIF LET lib_fat16_last_write_cluster = active_cluster //----------------------------------------------------- //TASK3 - rewind until we reach the end of the free area. //LET active_link = active_cluster LET cluster_search_max = LIB_FAT16_WRITE_CLUSTERS_MAX WHILE active_link == LIB_FAT16_CLUSTER_FREE DEC cluster_search_max IF cluster_search_max == 0 BREAK ENDIF DEC active_cluster LET active_link = active_cluster CALL lib_fat16_int_get_cluster_link ( active_link callstat ) IF callstat != 0 SUBEND //internal error ENDIF WEND IF active_cluster < 2 LET active_cluster = 1 ENDIF INC active_cluster //this one was used so we need the next one (the first free) LET lib_fat16_first_write_cluster = active_cluster //now we should have found a area free for writing. //MHTAG 20051114 - debug writes out the free area to the screen //CALL lib_dbg_hexoutw ( lib_fat16_first_write_cluster ) //CALL lib_dbg_outlf //CALL lib_dbg_hexoutw ( lib_fat16_last_write_cluster ) //CALL lib_dbg_outlf LET lib_fat16_int_fu_status = 0 //if we got this far it's a success FEND #IFEND LABEL lib_fat16low_skip #IFEND