diff --git a/CMakeLists.txt b/CMakeLists.txt index b6e1ceb..1f1cbaf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,4 +10,6 @@ add_library(embpart embpart.c fs/fat32/fat32.c fs/fat32/fat32.h MassStorage.c - MassStorage.h) \ No newline at end of file + MassStorage.h + file_interface.c + file_interface.h) \ No newline at end of file diff --git a/file_interface.c b/file_interface.c new file mode 100644 index 0000000..68e3ce2 --- /dev/null +++ b/file_interface.c @@ -0,0 +1,13 @@ +// +// Created by epagris on 2023.10.03.. +// + +#include "file_interface.h" + +ssize_t fi_read(void *ptr, size_t size, File *f) { + return f->read(ptr, size, f); +} + +ssize_t fi_seek(File *f, int32_t offset, uint8_t whence) { + return f->seek(f, offset, whence); +} diff --git a/file_interface.h b/file_interface.h new file mode 100644 index 0000000..a625d56 --- /dev/null +++ b/file_interface.h @@ -0,0 +1,16 @@ +#ifndef EMBPART_FILE_INTERFACE_H +#define EMBPART_FILE_INTERFACE_H + +#include +#include + +typedef struct File_ { + uint8_t data[32]; + ssize_t (*read)(void * ptr, size_t size, struct File_ * f); + ssize_t (*seek)(struct File_ * f, int32_t offset, uint8_t whence); +} File; + +ssize_t fi_read(void * ptr, size_t size, File * f); +ssize_t fi_seek(File * f, int32_t offset, uint8_t whence); + +#endif //EMBPART_FILE_INTERFACE_H diff --git a/fs/fat32/fat32.c b/fs/fat32/fat32.c index ff9f347..08347c5 100644 --- a/fs/fat32/fat32.c +++ b/fs/fat32/fat32.c @@ -12,14 +12,30 @@ #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; @@ -48,11 +64,21 @@ int fat32_load(Fat32_CtrlBlock *ctrl, uint32_t bpb_s, const MassStorage *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 > ' ') { @@ -63,6 +89,13 @@ uint32_t fat32_trim_str_right(char *str) { 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; @@ -81,6 +114,13 @@ uint16_t fat32_extract_lfn_text(const Fat32_LongFileNameEntry *lfnEntry, char *s #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; @@ -132,6 +172,12 @@ uint16_t fat32_get_file_entry(Fat32_FileEntry **const entry, char *entry_name) { 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); @@ -165,12 +211,35 @@ int fat32_print_file_entry(Fat32_FileEntry **const entry) { #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 + * @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) { FETCH_MSTG; @@ -194,6 +263,9 @@ const Fat32_FileEntry *fat32_locate_file(const Fat32_CtrlBlock *ctrl, const char 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 @@ -224,6 +296,8 @@ const Fat32_FileEntry *fat32_locate_file(const Fat32_CtrlBlock *ctrl, const char // 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... @@ -232,7 +306,7 @@ const Fat32_FileEntry *fat32_locate_file(const Fat32_CtrlBlock *ctrl, const char 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 = (entry->first_cluster_high << 16) | (entry->first_cluster_low); + uint32_t next_cluster = fat32_get_first_cluster(entry); uint32_t next_sector = fat32_get_first_sector_of_cluster(ctrl, next_cluster); // load sectors @@ -252,50 +326,80 @@ const Fat32_FileEntry *fat32_locate_file(const Fat32_CtrlBlock *ctrl, const char } } +// 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); - } - } - +// // 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 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; } -static inline uint32_t fat32_get_cluster_from_pos(const Fat32_CtrlBlock * ctrl, uint32_t first_cluster, uint32_t pos) { +/** + * Gets 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. + * @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 + 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 @@ -307,18 +411,82 @@ static inline uint32_t fat32_get_cluster_from_pos(const Fat32_CtrlBlock * ctrl, uint32_t next_cluster = (*fat_iter) & ~(0xF << 28); if (next_cluster >= 2 && next_cluster <= 0xFFFFFEF) { cluster = next_cluster; - } + } // TODO last chained cluster? } return cluster; } -int fat32_read_file(const Fat32_CtrlBlock * ctrl, const Fat32_FileEntry * entry, uint32_t pos, uint32_t len, uint8_t * p) { +/** + * 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". + * @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(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 = (entry->first_cluster_high << 16) | (entry->first_cluster_low); - uint32_t cluster = fat32_get_cluster_from_pos(ctrl, first_cluster, pos); - uint32_t sector = fat32_get_first_sector_of_cluster(ctrl, cluster); + 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); - uint32_t pos_in_sector = pos % ctrl->bytes_per_sector; - memcpy(p, buffer + pos_in_sector, len); - return len; // TODO + + // 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; } \ No newline at end of file diff --git a/fs/fat32/fat32.h b/fs/fat32/fat32.h index 8e2a4d5..3176ca8 100644 --- a/fs/fat32/fat32.h +++ b/fs/fat32/fat32.h @@ -91,11 +91,24 @@ typedef struct { uint16_t ustrBC[2]; // characters 11-12 } __attribute__((packed)) Fat32_LongFileNameEntry; +typedef struct { + uint32_t pos; // byte position + uint32_t cluster; // cluster corresponding to the position + uint32_t sector; // sector of the last read + uint32_t sector_begin_pos; // byte position of the sector's begin +} Fat32_SeekHint; + +typedef struct { + Fat32_CtrlBlock * ctrl; // pointer FAT32 control block + Fat32_SeekHint last_pos; // last read position + uint32_t entry_pos; // FileEntry byte position +} Fat32_FileHelper; + int fat32_load(Fat32_CtrlBlock *ctrl, uint32_t bpb_s, const MassStorage * mstg); int fat32_list_dir(const Fat32_CtrlBlock *ctrl, const char * dir); -int fat32_read_file(const Fat32_CtrlBlock * ctrl, const Fat32_FileEntry * entry, uint32_t pos, uint32_t len, uint8_t * p); +uint32_t fat32_read_file(const Fat32_CtrlBlock * ctrl, const Fat32_FileEntry * entry, uint32_t pos, uint32_t len, uint8_t * p); const Fat32_FileEntry * fat32_locate_file(const Fat32_CtrlBlock *ctrl, const char *path);