#include #include #include "fat32.h" #include "../../MassStorage.h" #define MIN(a, b) (((a) < (b)) ? (a) : (b)) #define SECTOR_SIZE (512) static uint8_t buffer[2 * SECTOR_SIZE]; // TODO access to this variable should be protected! /** * Copy first buffer to the second one */ static inline void fat32_copy_second_to_first_buffer() { memcpy(buffer, buffer + SECTOR_SIZE, SECTOR_SIZE); } #define FETCH_MSTG const MassStorage * mstg = ctrl->mstg #define READ_MSTG_TO_FIRST_BUFFER(idx) MSTG_READ(mstg, (idx), buffer) #define READ_MSTG_TO_SECOND_BUFFER(idx) MSTG_READ(mstg, (idx), buffer + SECTOR_SIZE) /** * Check if entry is in the second half of the buffer and load the next sector if it was. Also maintain the sector counter and * the *entry pointer * @param ctrl pointer to FAT32 control block * @param entry pointer to pointer (sic!) to a file entry residing in the buffer * @param sector pointer to sector count corresponding to the first hald of the working buffer */ static inline void fat32_check_sector_crossing_and_load(const Fat32_CtrlBlock * ctrl, Fat32_FileEntry ** entry, uint32_t * sector) { // if we crossed the first buffer - second buffer border, then // copy the second buffer to the first one and load the next sector // to the second buffer if ((void *) *entry > (void *) (buffer + SECTOR_SIZE)) { // copy the second buffer to the first one fat32_copy_second_to_first_buffer(); (*sector)++; FETCH_MSTG; READ_MSTG_TO_SECOND_BUFFER(*sector + 1); // modify the entry pointer, since it's been moved *entry = (Fat32_FileEntry *) (((uint8_t *) (*entry)) - SECTOR_SIZE); } // TODO also must pay attention on crossing not just sector, but cluster boundaries } /** * Get first sector of a cluster. A cluster may be composed of multiple sectors. * @param ctrl Pointer to FAT32 control block * @param cluster sequence number of a cluster * @return sequence number of the cluster's first sector */ static inline uint32_t fat32_get_first_sector_of_cluster(const Fat32_CtrlBlock *ctrl, uint32_t cluster) { return (cluster - 2) * ctrl->sectors_per_cluster + ctrl->data_s; } /** * Load FAT32 file system. Cannot load FAT12 or FAT16. * @param ctrl pointer to UNFILLED FAT32 control block * @param bpb_s sequence number of the BIOS Parameter Blocks' sector * @param mstg pointer to az existing Mass Storage object (holding read and write functions) * @return 0 if loading was successful, anything else indicates loading failure */ int fat32_load(Fat32_CtrlBlock *ctrl, uint32_t bpb_s, const MassStorage *mstg) { ctrl->mstg = mstg; ctrl->bpb_s = bpb_s; // load Boot Parameter Block mstg->read_sector(mstg, bpb_s, buffer); const Bpb_Fat32 *bpb_obj = (Bpb_Fat32 *) buffer; ctrl->bytes_per_sector = bpb_obj->bytes_per_sector; ctrl->sectors_per_cluster = bpb_obj->sectors_per_cluster; ctrl->sectors_per_fat = bpb_obj->n_sectors_per_fat; memcpy(ctrl->volume_label, bpb_obj->volume_label, 11); ctrl->serial_number = bpb_obj->volume_serial_number; ctrl->fat_copies = bpb_obj->n_fats; ctrl->fat_entries_per_sector = ctrl->bytes_per_sector / sizeof(Fat32_FatTableEntry); ctrl->fat_s = ctrl->bpb_s + bpb_obj->n_reserved_sectors; // FATs are stored on the hidden area right after the reserved sectors ctrl->data_s = ctrl->fat_s + ctrl->fat_copies * ctrl->sectors_per_fat; // data region begins right after the FAT copies ctrl->root_s = fat32_get_first_sector_of_cluster(ctrl, bpb_obj->root_first_cluster); // first sector of root directory is determined using the cluster number as an offset return 0; } /** * Dummy function converting 16-bit Unicode character to UTF-8 type. Just chops the upper byte * @param unicode Unicode character * @return UTF-8 character */ // TODO some dirty function to chop the high byte from the unicode charcode... static inline uint8_t fat32_unicode_to_utf8(uint16_t unicode) { return unicode & 0x00FF; } /** * Function, that trims the trailing whitespaces. The function returns with the trimmed string length. * @param str string to trim * @return trimmed length */ uint32_t fat32_trim_str_right(char *str) { uint32_t len = 0; while (*str > ' ') { str++; len++; } *str = '\0'; return len; } /** * Function that fetches the non-continuously stored Long File Name from an LFN-entry. * @param lfnEntry pointer to an LFN entry * @param str pointer to string destination buffer * @return string length (=13) */ // TODO loop unroll! uint16_t fat32_extract_lfn_text(const Fat32_LongFileNameEntry *lfnEntry, char *str) { uint16_t idx = 0; for (uint8_t i = 0; i < 5; i++) { // first block str[idx++] = (char) fat32_unicode_to_utf8(((uint8_t *) lfnEntry->ustr04)[2 * i]); } for (uint8_t i = 0; i < 6; i++) { // second block str[idx++] = (char) fat32_unicode_to_utf8(((uint8_t *) lfnEntry->ustr5A)[2 * i]); } for (uint8_t i = 0; i < 2; i++) { // third block str[idx++] = (char) fat32_unicode_to_utf8(((uint8_t *) lfnEntry->ustrBC)[2 * i]); } return idx; } #define LONG_FILE_NAME_ATTRIB_TEST(a) ((a) & (1 << 3)) && ((a) & (1 << 2)) && ((a) & (1 << 1)) && ((a) & (1)) #define LINELENGTH (32) /** * Gets the next file entry from a list. LFN entries preceding the seeked file entry are unified, entry is advanced, * so that at return *entry points to the actual file entry and not to some LFN fragments. * @param entry pointer to pointer (sic!) of an entry * @param entry_name pointer to entry name writeback area * @return length of entry name (may be used to avoid strlen() calls) */ uint16_t fat32_get_file_entry(Fat32_FileEntry **const entry, char *entry_name) { //static char entry_name[261]; bool done = false; bool reading_lfn = false; uint8_t rewind = 0; uint8_t attribs; // look for the first non-LFN entry and count the LFNs while (!done) { attribs = (*entry)->attributes; if (LONG_FILE_NAME_ATTRIB_TEST(attribs)) { // if this is an LFN-entry rewind++; reading_lfn = true; (*entry)++; // advance in the table } else { // if this is NOT an LFN-entry done = true; } } Fat32_FileEntry *const finalEntry = *(entry); // final, non-LFN entry attribs = finalEntry->attributes; uint16_t name_len = 0; // print the name of the entry if (reading_lfn) { // if the entry has a long name const Fat32_LongFileNameEntry *lfnEntry = (const Fat32_LongFileNameEntry *) (finalEntry - 1); // by order first (by address last) LFN entry // fetch long name for (uint8_t i = 0; i < rewind; i++) { name_len += fat32_extract_lfn_text(lfnEntry, entry_name + name_len); lfnEntry--; // retreat entry } entry_name[name_len] = '\0'; // terminate string name_len = fat32_trim_str_right(entry_name); // trim residual whitespaces } else { // if the entry has only a short name memcpy(entry_name, finalEntry->short_fname, 8); // copy basename entry_name[8] = '\0'; name_len = fat32_trim_str_right(entry_name); // trim whitespaces if (!((attribs & FAT32_FATT_VOLUME) || (attribs & FAT32_FATT_DIRECTORY))) { // omit dot if volume label or directory entry_name[name_len++] = '.'; // insert dot } memcpy(entry_name + name_len, finalEntry->extension, 3); // copy extension name_len = fat32_trim_str_right(entry_name); // trim whitespaces if (!(attribs & FAT32_FATT_DIRECTORY) && (entry_name[name_len - 1] == '.')) { // chop dot if no extension and not directory name_len--; } entry_name[name_len] = '\0'; // terminate string } return name_len; } /** * Print a file entry: NAME ATTRIBS SIZE. Contains calls to fat32_get_file_entry(), that's why entry is maintained * the same way as documented at fat32_get_file_entry(). * @param entry pointer to pointer (sic!) to a file entry. See fat32_get_file_entry() docs. * @return 0 if successful */ int fat32_print_file_entry(Fat32_FileEntry **const entry) { static char entry_name[261]; uint16_t name_len = fat32_get_file_entry(entry, entry_name); uint8_t attribs = (*entry)->attributes; // print entry type char entry_type[5] = {'F', ' ', ' ', ' ', '\0'}; // file, by default if (attribs & FAT32_FATT_DIRECTORY) { entry_type[0] = 'D'; } else if (attribs & FAT32_FATT_VOLUME) { entry_type[0] = 'V'; } else if (attribs & FAT32_FATT_HIDDEN) { entry_type[1] = 'H'; } else if (attribs & FAT32_FATT_READ_ONLY) { entry_type[2] = 'R'; } else if (attribs & FAT32_FATT_SYSTEM) { entry_type[3] = 'S'; } uint16_t padding = 1; if (name_len < LINELENGTH) { padding = LINELENGTH - name_len; } MSG("%s%*c%s %u\n", entry_name, padding, ' ', entry_type, (*entry)->size); return 0; } #define FAT32_UNUSED_FILE_ENTRY (0xE5) #define FAT32_DIRECTORY_SEPARATOR ('/') #define FAT32_MAX_BASENAME_LENGTH (47) /** * Extracts the next basename from a full path. Does not modify the path string, only returns with * the length of the next basename token. Like a simplified strtok(). * @param path pointer to full path * @return next basename token length */ static inline uint16_t fat32_extract_next_basename_length(const char *path) { uint16_t i; for (i = 0; (path[i] != FAT32_DIRECTORY_SEPARATOR) && (path[i] != '\0'); i++) {} return i; } /** * Get the sequence number of the first cluster of a file entry. * @param entry pointer to a file entry * @return sequence number of the first cluster of a file entry */ static inline uint32_t fat32_get_first_cluster(const Fat32_FileEntry *entry) { return (entry->first_cluster_high << 16) | (entry->first_cluster_low); } /** * The function locates a file based on its full path. If path is empty and root is requested, then it returns pointer to * the first root directory ENTRY, not to a entry pointing to the root directory. * @param ctrl pointer to a FAT32 control block * @param path full path of the file to be located * @param file_entry_ba if not NULL, then the function returns here the file entry's byte address (on the mass storage); untouched if file not found * @return pointer to the file entry. Caution! This pointer is only valid until no further * call is made to fat32_* functions, since all functions involve the same buffer. * Functions invalidating buffer content are marked with (I). */ const Fat32_FileEntry *fat32_locate_file(const Fat32_CtrlBlock *ctrl, const char *path, uint32_t *file_entry_ba) { FETCH_MSTG; // start search in the root directory uint32_t sector = ctrl->root_s; READ_MSTG_TO_FIRST_BUFFER(sector); READ_MSTG_TO_SECOND_BUFFER(sector + 1); bool file_found = false; // file is not found in the beginning bool no_such_file = false; // assume that there is such a file const char *path_iter = path; // path is being fetched from its beginning char basename[FAT32_MAX_BASENAME_LENGTH + 1]; // basename basename[FAT32_MAX_BASENAME_LENGTH] = '\0'; // NULL-termination char entry_name[FAT32_MAX_BASENAME_LENGTH + 1]; // entry name Fat32_FileEntry *entry = NULL; // entry iterator // handle opening the root, return pointer to first ENTRY in the root directory (see description!) if (path[0] == '\0') { file_found = true; // the root directory always exists entry = (Fat32_FileEntry *) buffer; // root directory ENTRIES is in the root sector } // if not root directory, then... while ((!file_found) && (!no_such_file)) { // traversing tree levels // extract first basename uint16_t basename_len = fat32_extract_next_basename_length(path_iter); // get basename length uint16_t copy_len = MIN(basename_len, FAT32_MAX_BASENAME_LENGTH); memcpy(basename, path_iter, copy_len); // extract basename basename[copy_len] = '\0'; // NULL-termination path_iter += basename_len; // advance basename if (*path_iter == FAT32_DIRECTORY_SEPARATOR) { // skip directory separator, but don't skip zero termination path_iter++; } // search for basename in the current directory // acquire pointer to the beginning of the file entry table entry = (Fat32_FileEntry *) buffer; bool basename_entry_found = false; while ((entry->short_fname[0] != 0x00) && (!basename_entry_found)) { // traversing siblings if (entry->short_fname[0] != FAT32_UNUSED_FILE_ENTRY) { fat32_get_file_entry(&entry, entry_name); // compare entry name if (!strncmp(entry_name, basename, basename_len)) { // if matches... basename_entry_found = true; // ...then the entry is found continue; } } entry++; // check sector and cluster boundary crossing fat32_check_sector_crossing_and_load(ctrl, &entry, §or); // // if we crossed the first buffer - second buffer border, then // // copy the second buffer to the first one and load the next sector // // to the second buffer // if ((void *) entry > (void *) (buffer + SECTOR_SIZE)) { // // copy the second buffer to the first one // fat32_copy_second_to_first_buffer(); // sector++; // READ_MSTG_TO_SECOND_BUFFER(sector + 1); // // // modify the entry pointer, since it's been moved // entry = (Fat32_FileEntry *) (((uint8_t *) (entry)) - SECTOR_SIZE); // } // TODO also must pay attention on crossing not just sector, but cluster boundaries } // if the basename entry is found... if (basename_entry_found) { if (path_iter[0] == '\0') { // ...and it's the end of the path file_found = true; // ...then the file is found } else { // ...if it's not the end of the path, then load the cluster corresponding to the entry // determine sector number uint32_t next_cluster = fat32_get_first_cluster(entry); uint32_t next_sector = fat32_get_first_sector_of_cluster(ctrl, next_cluster); // load sectors READ_MSTG_TO_FIRST_BUFFER(next_sector); READ_MSTG_TO_SECOND_BUFFER(next_sector + 1); sector = next_sector; // advance sector } } else { // if not found... no_such_file = true; // ...then the file could not be located } } // return with appropriate value if (!no_such_file) { if (file_entry_ba != NULL) { // if file entry writeback address if provided *file_entry_ba = sector * ctrl->bytes_per_sector + (((uint8_t *) entry) - buffer); } return entry; } else { return NULL; } } /** * Get the sequence number of the sector holding the byte pointed by address. * @param ctrl pointer to FAT32 control block * @param address byte address * @return sequence number of the requested sector */ static inline uint32_t fat32_get_sector_by_byte_address(const Fat32_CtrlBlock *ctrl, uint32_t ba) { return ba / ctrl->bytes_per_sector; } /** * Get file entry based on its byte address. (I) * @param ctrl pointer to FAT32 control block * @param ba byte address of the file entry * @return pointer to file entry */ static inline const Fat32_FileEntry *fat32_get_file_entry_by_byte_address(const Fat32_CtrlBlock *ctrl, uint32_t ba) { uint32_t sector = fat32_get_sector_by_byte_address(ctrl, ba); uint32_t offset = ba % ctrl->bytes_per_sector; FETCH_MSTG; READ_MSTG_TO_FIRST_BUFFER(sector); return (Fat32_FileEntry *) (buffer + offset); } /** * Get the sector's sequence number corresponding to the specific cluster identified by its sequence number. * @param ctrl pointer to FAT32 control block * @param cluster sequence number of a cluster * @return sequence number of the sector holding the FAT entry corresponding to the cluster */ static inline uint32_t fat32_get_fat_entry_sector_by_cluster_index(const Fat32_CtrlBlock *ctrl, uint32_t cluster) { return cluster * sizeof(Fat32_FatTableEntry) / ctrl->bytes_per_sector + ctrl->fat_s; } /** * Get the sequence number of the next chained cluster. (I) * @param ctrl pointer to FAT32 control block * @param cluster sequence number of a cluster * @return sequence number of the cluster following the passed one in the chain */ static inline uint32_t fat32_get_next_chained_cluster(const Fat32_CtrlBlock *ctrl, uint32_t cluster) { FETCH_MSTG; uint32_t sector = fat32_get_fat_entry_sector_by_cluster_index(ctrl, cluster); // fetch read FAT entry's sector READ_MSTG_TO_FIRST_BUFFER(sector); Fat32_FatTableEntry *fat_iter = ((Fat32_FatTableEntry *) buffer) + (cluster % ctrl->fat_entries_per_sector); // get FAT iter uint32_t next_cluster = (*fat_iter) & ~(0xF << 28); // mask lower 28-bits return next_cluster; } /** * Get sequence number of the cluster storing a specific byte position (pos) if the file's first cluster is given. (I) * @param ctrl pointer to FAT32 control block * @param first_cluster sequence number of the first cluster of the file * @param pos byte position * @return sequence number of the cluster */ static inline uint32_t fat32_get_cluster_from_pos(const Fat32_CtrlBlock *ctrl, uint32_t first_cluster, uint32_t pos) { FETCH_MSTG; uint32_t cluster_link_number = pos / (ctrl->sectors_per_cluster * ctrl->bytes_per_sector); // calculate the sequence number of the FAT entry containing the reference to the desired data cluster uint32_t fat_iter_sector = 0; uint32_t cluster = first_cluster; // linked cluster Fat32_FatTableEntry *fat_iter = NULL; // iterator on FAT table for (uint32_t hop = 0; hop < cluster_link_number; hop++) { uint32_t new_fat_iter_sector = fat32_get_fat_entry_sector_by_cluster_index(ctrl, cluster); // get the FAT sector containing the referred entry if (new_fat_iter_sector != fat_iter_sector) { READ_MSTG_TO_FIRST_BUFFER(new_fat_iter_sector); // read FAT sector containing the specific entry to the buffer fat_iter_sector = new_fat_iter_sector; } fat_iter = ((Fat32_FatTableEntry *) buffer) + (cluster % ctrl->fat_entries_per_sector); // get FAT iter uint32_t next_cluster = (*fat_iter) & ~(0xF << 28); if (next_cluster >= 2 && next_cluster <= 0xFFFFFEF) { cluster = next_cluster; } // TODO last chained cluster? } return cluster; } /** * Gets how many bytes a cluster contains. * @param ctrl pointer to FAT32 control block * @return bytes per cluster */ static inline uint32_t fat32_get_bytes_per_cluster(const Fat32_CtrlBlock *ctrl) { return ctrl->bytes_per_sector * ctrl->sectors_per_cluster; } /** * Read a file using "random access". (I) * This function holds a load of redundant code, but this is with intent. * @param ctrl pointer to FAT32 control block * @param entry pointer to the corresponding file entry * @param pos read start position * @param len read length * @param p pointer to output buffer * @return the number of bytes read */ uint32_t fat32_read_file_random_access(const Fat32_CtrlBlock *ctrl, const Fat32_FileEntry *entry, uint32_t pos, uint32_t len, uint8_t *p) { // check position validity and check that the entry corresponds to a file, not to a directory or volume uint8_t attr = entry->attributes; if ((pos >= entry->size) || (attr & FAT32_FATT_DIRECTORY) || (attr & FAT32_FATT_VOLUME)) { return 0; } FETCH_MSTG; uint32_t first_cluster = fat32_get_first_cluster(entry); // get first cluster of the entry uint32_t cluster = fat32_get_cluster_from_pos(ctrl, first_cluster, pos); // get cluster sequence number of the file using the read position uint32_t first_sector = fat32_get_first_sector_of_cluster(ctrl, cluster); // get the first sector of the cluster uint32_t bytes_per_cluster = fat32_get_bytes_per_cluster(ctrl); // get number of bytes per cluster uint32_t pos_in_cluster = pos % bytes_per_cluster; // transform read position to a relative read position from the cluster beginning uint32_t sector_of_cluster = pos_in_cluster / ctrl->bytes_per_sector; // get the sequence number of sector in the cluster that will be read uint32_t sector = first_sector + sector_of_cluster; // offset the sectors based on relative read position uint32_t pos_in_sector = pos % ctrl->bytes_per_sector; // count read position relative from sector's beginning READ_MSTG_TO_FIRST_BUFFER(sector); // cap length, don't read after the end of the file uint32_t read_len = MIN(len, entry->size - pos); uint32_t offset = 0; // reading from a file may involve reading across cluster boundaries // 1. read the beginning from the first cluster uint32_t first_read_len = MIN(read_len, ctrl->bytes_per_sector - pos); memcpy(p, buffer + pos_in_sector, first_read_len); read_len -= first_read_len; // decrease the total read len with the number of bytes read from the first sector of the file offset += first_read_len; // advance offset // 2. read from intermediate sectors and read from the final sector // if the remaining read length is non-zero, then maintain buffered sectors while (read_len > 0) { sector_of_cluster++; // increase sector index if (sector_of_cluster >= ctrl->sectors_per_cluster) { // if we crossed a cluster boundary, then load the next cluster's first sector in chain cluster = fat32_get_next_chained_cluster(ctrl, cluster); // get next cluster first_sector = fat32_get_first_sector_of_cluster(ctrl, cluster); sector = first_sector; // set the sector number to the first sector of the cluster sector_of_cluster = 0; // it's the zeroth sector of the cluster READ_MSTG_TO_FIRST_BUFFER(sector); // load the newly determined cluster's first sector } else { // if we didn't cross a cluster boundary, then... sector++; //...just increase the sector count READ_MSTG_TO_FIRST_BUFFER(sector); // ...and load that sector } // copy the whole sector or some portion of the sector counting from the sector's begin uint32_t copy_size = MIN(ctrl->bytes_per_sector, read_len); memcpy(p + offset, buffer, copy_size); read_len -= copy_size; offset += copy_size; } return read_len; } // ----------------- int fat32_open_file(const Fat32_CtrlBlock *ctrl, const char *path, Fat32_FileHelper *file_helper) { uint32_t file_entry_ba; const Fat32_FileEntry *entry = fat32_locate_file(ctrl, path, &file_entry_ba); uint32_t first_cluster = fat32_get_first_cluster(entry); // store data usable for identifying file on the storage file_helper->ctrl = ctrl; file_helper->entry_ba = file_entry_ba; // store file information file_helper->size = entry->size; // seek to zero file_helper->last_pos.pos = 0; file_helper->last_pos.sector_begin_pos = 0; file_helper->last_pos.cluster = first_cluster; file_helper->last_pos.sector = fat32_get_first_sector_of_cluster(ctrl, first_cluster); file_helper->last_pos.sector_of_cluster = 0; return 0; } /** * Seek file stream, set reader to pos. * @param ctrl pointer to FAT32 control block * @param pos stream position * @param file_helper pointer to file helper, which gets updated * @return 0 on success */ uint32_t fat32_seek_stream(const Fat32_CtrlBlock *ctrl, uint32_t pos, Fat32_FileHelper *file_helper) { // check that we are not attempting to seek out of the file if (pos >= file_helper->size) { return -1; } // actual seeking Fat32_SeekHint *seek_hint = &file_helper->last_pos; // fetch seek hint if (pos > file_helper->last_pos.pos) { // if seeking forward, then seeking can be accelerated using the seek hints uint32_t delta = pos - file_helper->last_pos.pos; uint32_t bytes_to_sector_end = ctrl->bytes_per_sector - (file_helper->last_pos.pos % ctrl->bytes_per_sector); // determine byte count to sector end if (delta < bytes_to_sector_end) { // if seeking does not cross sector boundary seek_hint->pos = pos; // just change position } else { // ...if crosses sector boundary uint32_t sector_boundaries_crossed = 1 + (delta - bytes_to_sector_end) / ctrl->bytes_per_sector; // 1: boundary crossed; further boundaries crossed based on delta uint32_t delta_sector_from_clusters_first_sector = seek_hint->sector_of_cluster + sector_boundaries_crossed; uint32_t cluster_boundaries_crossed = delta_sector_from_clusters_first_sector / ctrl->sectors_per_cluster; // number of cluster boundary crossing if (cluster_boundaries_crossed > 0) { // if we crossed a cluster boundary, then load the next cluster's first sector in chain uint32_t cluster = seek_hint->cluster; for (uint32_t i = 0; i < cluster_boundaries_crossed; i++) { // do the cluster crossings cluster = fat32_get_next_chained_cluster(ctrl, cluster); // get next cluster } // update position fields seek_hint->cluster = cluster; seek_hint->sector_of_cluster = delta_sector_from_clusters_first_sector % ctrl->sectors_per_cluster; seek_hint->sector += sector_boundaries_crossed; seek_hint->pos = pos; seek_hint->sector_begin_pos += sector_boundaries_crossed * ctrl->bytes_per_sector; } } } else { // if seeking backwards, then no acceleration is available const Fat32_FileEntry *entry = fat32_get_file_entry_by_byte_address(ctrl, file_helper->entry_ba); // fetch file entry uint32_t first_cluster = fat32_get_first_cluster(entry); // get first cluster of the entry uint32_t cluster = fat32_get_cluster_from_pos(ctrl, first_cluster, pos); // get cluster sequence number of the file using the read position uint32_t first_sector = fat32_get_first_sector_of_cluster(ctrl, cluster); // get the first sector of the cluster uint32_t bytes_per_cluster = fat32_get_bytes_per_cluster(ctrl); // get number of bytes per cluster uint32_t pos_in_cluster = pos % bytes_per_cluster; // transform read position to a relative read position from the cluster beginning uint32_t sector_of_cluster = pos_in_cluster / ctrl->bytes_per_sector; // get the sequence number of sector in the cluster that will be read uint32_t sector = first_sector + sector_of_cluster; // offset the sectors based on relative read position // update position fields seek_hint->pos = pos; seek_hint->sector_of_cluster = sector_of_cluster; seek_hint->cluster = cluster; seek_hint->sector = first_sector + sector_of_cluster; seek_hint->sector_begin_pos = pos - (pos % ctrl->bytes_per_sector); } return 0; } uint32_t fat32_read_file_stream(const Fat32_CtrlBlock *ctrl, Fat32_FileHelper *file_helper, uint32_t len, uint8_t *p) { FETCH_MSTG; Fat32_SeekHint *seek_hint = &file_helper->last_pos; uint32_t pos_in_sector = seek_hint->pos - seek_hint->sector_begin_pos; // determine position in current sector READ_MSTG_TO_FIRST_BUFFER(seek_hint->sector); // cap length, don't read after the end of the file uint32_t read_len = MIN(len, file_helper->size - seek_hint->pos); uint32_t offset = 0; uint32_t final_read_len = read_len; // a variable we will return with // reading from a file may involve reading across cluster boundaries // 1. read the beginning from the first cluster uint32_t first_read_len = MIN(read_len, ctrl->bytes_per_sector - seek_hint->pos); memcpy(p, buffer + pos_in_sector, first_read_len); read_len -= first_read_len; // decrease the total read len with the number of bytes read from the first sector of the file offset += first_read_len; // advance offset seek_hint->pos += first_read_len; // advance seek pos // 2. read from intermediate sectors and read from the final sector // if the remaining read length is non-zero, then maintain buffered sectors while (read_len > 0) { seek_hint->sector_of_cluster++; // increase sector index if (seek_hint->sector_of_cluster >= ctrl->sectors_per_cluster) { // if we crossed a cluster boundary, then load the next cluster's first sector in chain seek_hint->cluster = fat32_get_next_chained_cluster(ctrl, seek_hint->cluster); // get next cluster seek_hint->sector = fat32_get_first_sector_of_cluster(ctrl, seek_hint->cluster); seek_hint->sector_of_cluster = 0; // it's the zeroth sector of the cluster READ_MSTG_TO_FIRST_BUFFER(seek_hint->sector); // load the newly determined cluster's first sector } else { // if we didn't cross a cluster boundary, then... seek_hint->sector++; //...just increase the sector count READ_MSTG_TO_FIRST_BUFFER(seek_hint->sector); // ...and load that sector } seek_hint->sector_begin_pos += ctrl->bytes_per_sector; // advance begin position, we have stepped one sector forward // copy the whole sector or some portion of the sector counting from the sector's begin uint32_t copy_size = MIN(ctrl->bytes_per_sector, read_len); memcpy(p + offset, buffer, copy_size); read_len -= copy_size; offset += copy_size; seek_hint->pos += copy_size; } return final_read_len; } // ---------------------- /** * List directory. (I) * @param ctrl pointer to FAT32 control block * @param dir path of directory being listed * @return 0 if successful, anything else refers to errors */ int fat32_list_dir(const Fat32_CtrlBlock *ctrl, const char *dir) { uint32_t cluster, sector; const Fat32_FileEntry *dir_entry = NULL; if (dir[0] != '\0') { // if listing anything different from root directory dir_entry = fat32_locate_file(ctrl, dir, NULL); // search dir entry if (dir_entry == NULL) { // if file is not found at all MSG("Directory '%s' could not be located!\n", dir); return -1; } else if ((dir[0] != '\0') && !(dir_entry->attributes & FAT32_FATT_DIRECTORY)) { // if found but it's no directory MSG("It's not a directory!\n"); return 0; } cluster = fat32_get_first_cluster(dir_entry); // get first cluster sector = fat32_get_first_sector_of_cluster(ctrl, cluster); // get first (absolute) sector of the cluster } else { // if listing from the root sector sector = ctrl->root_s; // get root directory's (absolute) sector } // fill buffers FETCH_MSTG; READ_MSTG_TO_FIRST_BUFFER(sector); READ_MSTG_TO_SECOND_BUFFER(sector + 1); // acquire pointer to the beginning of the file entry table Fat32_FileEntry *entry = (Fat32_FileEntry *) buffer; while (entry->short_fname[0] != 0x00) { if (entry->short_fname[0] != FAT32_UNUSED_FILE_ENTRY) { fat32_print_file_entry(&entry); } entry++; // check sector and cluster boundary crossing fat32_check_sector_crossing_and_load(ctrl, &entry, §or); } return 0; } /** * Get FAT32 volume label. * @param ctrl pointer to FAT32 control block * @param label free space of at least 12 bytes (including terminating zero) for storing volume label * @param maxLen maximum string length */ void fat32_get_volume_label(const Fat32_CtrlBlock * ctrl, char * label, uint16_t maxLen) { // fill buffers char entry_name[261]; uint32_t sector = ctrl->root_s; FETCH_MSTG; READ_MSTG_TO_FIRST_BUFFER(sector); READ_MSTG_TO_SECOND_BUFFER(sector + 1); // acquire pointer to the beginning of the file entry table Fat32_FileEntry *entry = (Fat32_FileEntry *) buffer; while (entry->short_fname[0] != 0x00) { if (entry->short_fname[0] != FAT32_UNUSED_FILE_ENTRY) { fat32_get_file_entry(&entry, entry_name); if (entry->attributes & FAT32_FATT_VOLUME) { // search for volume name entry strncpy(label, entry_name, maxLen); label[maxLen] = '\0'; } } entry++; // check sector and cluster boundary crossing fat32_check_sector_crossing_and_load(ctrl, &entry, §or); } } // ---------------------- static int fat32_drv_read(const void * ctrl, void * file, unsigned long len, void * buf) { return (int) fat32_read_file_stream((Fat32_CtrlBlock *) ctrl, (Fat32_FileHelper *) file, len, (uint8_t *) buf); } static int fat32_drv_seek(const void * ctrl, void * file, unsigned long pos) { return (int) fat32_seek_stream((Fat32_CtrlBlock *) ctrl, pos, (Fat32_FileHelper *) file); } Fs_Driver fat32_driver = { (fs_drv_open) fat32_open_file, // open (fs_drv_seek) fat32_drv_seek, // seek (fs_drv_read) fat32_drv_read, // read (fs_drv_list) fat32_list_dir // list files };