324 lines
13 KiB
C
324 lines
13 KiB
C
//
|
|
// Created by epagris on 2023.09.28..
|
|
//
|
|
|
|
#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!
|
|
|
|
static inline void fat32_copy_second_to_first_buffer() {
|
|
memcpy(buffer, buffer + SECTOR_SIZE, SECTOR_SIZE);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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)
|
|
|
|
// 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;
|
|
}
|
|
|
|
uint32_t fat32_trim_str_right(char *str) {
|
|
uint32_t len = 0;
|
|
while (*str > ' ') {
|
|
str++;
|
|
len++;
|
|
}
|
|
*str = '\0';
|
|
return len;
|
|
}
|
|
|
|
// 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)
|
|
|
|
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;
|
|
}
|
|
|
|
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)
|
|
|
|
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;
|
|
}
|
|
|
|
const Fat32_FileEntry *fat32_locate_file(const Fat32_CtrlBlock *ctrl, const char *path) {
|
|
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)) {
|
|
// 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
|
|
|
|
// 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)) {
|
|
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);
|
|
}
|
|
}
|
|
|
|
// 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 = (entry->first_cluster_high << 16) | (entry->first_cluster_low);
|
|
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);
|
|
}
|
|
} 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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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) {
|
|
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;
|
|
}
|
|
}
|
|
return cluster;
|
|
}
|
|
|
|
int fat32_read_file(const Fat32_CtrlBlock * ctrl, const Fat32_FileEntry * entry, uint32_t pos, uint32_t len, uint8_t * p) {
|
|
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);
|
|
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
|
|
} |