embpart/fs/fat32/fat32.c

657 lines
29 KiB
C

#include <memory.h>
#include <stdbool.h>
#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);
}
/**
* 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;
}
#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)
/**
* 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
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 (entry_name[name_len - 1] == '.') { // chop dot if no extension
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.
* @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
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++;
// 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
if (file_entry_ba != NULL) { // if file entry writeback address if provided
*file_entry_ba = sector * ctrl->bytes_per_sector + (((uint8_t *) entry) - buffer);
}
} 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) {
return entry;
} else {
return NULL;
}
}
// TODO subject to change, must include cluster crossing check
int fat32_list_dir(const Fat32_CtrlBlock *ctrl, const char *dir) {
// // TODO find directory
//
// // read root directory
// FETCH_MSTG;
// uint32_t sector = ctrl->root_s;
// 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++;
//
// // 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);
// }
// }
//
// return 0;
return 0;
}
/**
* 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;
// 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 read_len;
}
// ----------------------
Fs_Driver fat32_driver = { (fs_drv_open) fat32_open_file };