File syn_fat.c¶
File List > src > syntropic > storage > syn_fat.c
Go to the documentation of this file
#if __has_include("syn_config.h")
#include "syn_config.h"
#endif
#if !defined(SYN_USE_FAT) || SYN_USE_FAT
#if defined(SYN_USE_VFS) && !SYN_USE_VFS
#error "syn_fat requires SYN_USE_VFS=1"
#endif
#include "../log/syn_log.h"
#include "../util/syn_assert.h"
#include "../util/syn_pack.h"
#include "syn_fat.h"
#include <string.h>
#define TAG "syn_fat"
/* ── Custom FAT volume context ──────────────────────────────────────────── */
typedef struct {
uint16_t bytes_per_sector;
uint8_t sectors_per_cluster;
uint16_t reserved_sectors;
uint8_t num_fats;
uint16_t fat16_root_entries;
uint32_t fat_sectors;
uint32_t root_cluster;
bool is_fat32;
/* Calculated offsets (in sectors) */
uint32_t fat_start_sector;
uint32_t root_start_sector;
uint32_t root_sectors;
uint32_t data_start_sector;
} FAT_Volume;
static FAT_Volume g_vol;
SYN_SD g_sd;
/* ── Open file context pool ─────────────────────────────────────────────── */
typedef struct {
char name[11];
uint32_t start_cluster;
uint32_t size;
uint32_t offset;
uint32_t dir_sector;
uint32_t dir_offset;
int mode;
bool used;
} SYN_FatFileCtx;
static SYN_FatFileCtx g_fat_files[SYN_VFS_MAX_OPEN_FILES];
/* ── Helper Functions ───────────────────────────────────────────────────── */
static uint32_t find_partition_start(const uint8_t *sector0)
{
if (sector0[510] != 0x55 || sector0[511] != 0xAA)
return 0;
/* Check MBR partition 0 entry at offset 446 (0x1BE) */
const uint8_t *part = §or0[446];
uint8_t type = part[4];
if (type == 0x04 || type == 0x06 || type == 0x0B || type == 0x0C || type == 0x0E ||
type == 0x14 || type == 0x16 || type == 0x1B || type == 0x1C) {
return syn_peek_u32_le(part, 8);
}
return 0;
}
static bool fat_parse_bpb(FAT_Volume *vol, const uint8_t *bpb, uint32_t volume_start)
{
if (bpb[510] != 0x55 || bpb[511] != 0xAA)
return false;
vol->bytes_per_sector = syn_peek_u16_le(bpb, 11);
if (vol->bytes_per_sector != 512)
return false;
vol->sectors_per_cluster = bpb[13];
if (vol->sectors_per_cluster == 0)
return false;
vol->reserved_sectors = syn_peek_u16_le(bpb, 14);
vol->num_fats = bpb[16];
vol->fat16_root_entries = syn_peek_u16_le(bpb, 17);
uint16_t spf16 = syn_peek_u16_le(bpb, 22);
if (spf16 != 0) {
vol->fat_sectors = spf16;
vol->is_fat32 = false;
vol->root_cluster = 0;
} else {
vol->fat_sectors = syn_peek_u32_le(bpb, 36);
vol->root_cluster = syn_peek_u32_le(bpb, 44);
vol->is_fat32 = true;
}
vol->fat_start_sector = volume_start + vol->reserved_sectors;
if (vol->is_fat32) {
vol->root_start_sector = 0;
vol->root_sectors = 0;
vol->data_start_sector = vol->fat_start_sector + (vol->num_fats * vol->fat_sectors);
} else {
vol->root_start_sector = vol->fat_start_sector + (vol->num_fats * vol->fat_sectors);
vol->root_sectors = ((vol->fat16_root_entries * 32) + 511) / 512;
vol->data_start_sector = vol->root_start_sector + vol->root_sectors;
}
return true;
}
static uint32_t cluster_to_sector(const FAT_Volume *vol, uint32_t cluster)
{
if (cluster < 2)
return 0;
return vol->data_start_sector + (cluster - 2) * vol->sectors_per_cluster;
}
static uint32_t read_fat_entry(const FAT_Volume *vol, uint32_t cluster)
{
uint8_t sec_buf[512];
uint32_t fat_sector;
uint32_t offset;
if (vol->is_fat32) {
fat_sector = vol->fat_start_sector + ((cluster * 4) / 512);
offset = (cluster * 4) % 512;
} else {
fat_sector = vol->fat_start_sector + ((cluster * 2) / 512);
offset = (cluster * 2) % 512;
}
if (syn_sd_read(&g_sd, fat_sector, sec_buf) != SYN_OK) {
return 0x0FFFFFFF;
}
if (vol->is_fat32) {
uint32_t entry = syn_peek_u32_le(sec_buf, offset);
return entry & 0x0FFFFFFF;
} else {
uint16_t entry = syn_peek_u16_le(sec_buf, offset);
if (entry >= 0xFFF8)
return 0x0FFFFFFF;
return entry;
}
}
static bool write_fat_entry(const FAT_Volume *vol, uint32_t cluster, uint32_t value)
{
uint8_t sec_buf[512];
uint32_t fat_sector;
uint32_t offset;
if (vol->is_fat32) {
fat_sector = vol->fat_start_sector + ((cluster * 4) / 512);
offset = (cluster * 4) % 512;
} else {
fat_sector = vol->fat_start_sector + ((cluster * 2) / 512);
offset = (cluster * 2) % 512;
}
if (syn_sd_read(&g_sd, fat_sector, sec_buf) != SYN_OK)
return false;
if (vol->is_fat32) {
syn_poke_u32_le(value, sec_buf, offset);
} else {
syn_poke_u16_le((uint16_t)value, sec_buf, offset);
}
if (syn_sd_write(&g_sd, fat_sector, sec_buf) != SYN_OK)
return false;
if (vol->num_fats > 1) {
if (syn_sd_write(&g_sd, fat_sector + vol->fat_sectors, sec_buf) != SYN_OK)
return false;
}
return true;
}
static uint32_t find_free_cluster(const FAT_Volume *vol)
{
for (uint32_t c = 2; c < 65536; c++) {
uint32_t entry = read_fat_entry(vol, c);
if (entry == 0)
return c;
}
return 0;
}
static void path_to_fat_name(const char *path, char *fat_name)
{
if (path[0] == '/')
path++;
memset(fat_name, ' ', 11);
int i = 0;
while (path[i] != '\0' && path[i] != '.' && i < 8) {
char c = path[i];
if (c >= 'a' && c <= 'z')
c = c - 'a' + 'A';
fat_name[i] = c;
i++;
}
while (path[i] != '\0' && path[i] != '.') {
i++;
}
if (path[i] == '.') {
i++;
int j = 0;
while (path[i] != '\0' && j < 3) {
char c = path[i];
if (c >= 'a' && c <= 'z')
c = c - 'a' + 'A';
fat_name[8 + j] = c;
i++;
j++;
}
}
}
typedef struct {
uint32_t sector;
uint32_t offset;
uint32_t start_cluster;
uint32_t file_size;
bool found;
} DirEntryLoc;
static bool scan_root_dir(const FAT_Volume *vol, const char *fat_name, DirEntryLoc *loc,
bool find_empty_slot)
{
uint8_t sec_buf[512];
loc->found = false;
if (vol->is_fat32) {
uint32_t cluster = vol->root_cluster;
while (cluster < 0x0F000000) {
uint32_t start_sector = cluster_to_sector(vol, cluster);
for (uint8_t s = 0; s < vol->sectors_per_cluster; s++) {
uint32_t current_sector = start_sector + s;
if (syn_sd_read(&g_sd, current_sector, sec_buf) != SYN_OK)
return false;
for (uint32_t off = 0; off < 512; off += 32) {
uint8_t first_char = sec_buf[off];
if (first_char == 0x00 && !find_empty_slot) {
return false;
}
if (find_empty_slot) {
if (first_char == 0x00 || first_char == 0xE5) {
loc->sector = current_sector;
loc->offset = off;
loc->found = true;
return true;
}
} else {
if (first_char != 0xE5 && memcmp(&sec_buf[off], fat_name, 11) == 0) {
loc->sector = current_sector;
loc->offset = off;
loc->start_cluster =
(uint32_t)syn_peek_u16_le(&sec_buf[off], 26) |
((uint32_t)syn_peek_u16_le(&sec_buf[off], 20) << 16);
loc->file_size = syn_peek_u32_le(&sec_buf[off], 28);
loc->found = true;
return true;
}
}
}
}
cluster = read_fat_entry(vol, cluster);
}
} else {
for (uint32_t s = 0; s < vol->root_sectors; s++) {
uint32_t current_sector = vol->root_start_sector + s;
if (syn_sd_read(&g_sd, current_sector, sec_buf) != SYN_OK)
return false;
for (uint32_t off = 0; off < 512; off += 32) {
uint8_t first_char = sec_buf[off];
if (first_char == 0x00 && !find_empty_slot) {
return false;
}
if (find_empty_slot) {
if (first_char == 0x00 || first_char == 0xE5) {
loc->sector = current_sector;
loc->offset = off;
loc->found = true;
return true;
}
} else {
if (first_char != 0xE5 && memcmp(&sec_buf[off], fat_name, 11) == 0) {
loc->sector = current_sector;
loc->offset = off;
loc->start_cluster = (uint32_t)syn_peek_u16_le(&sec_buf[off], 26);
loc->file_size = syn_peek_u32_le(&sec_buf[off], 28);
loc->found = true;
return true;
}
}
}
}
}
return false;
}
static uint32_t get_cluster_for_offset(const FAT_Volume *vol, uint32_t start_cluster,
uint32_t offset)
{
uint32_t cluster_size = vol->sectors_per_cluster * 512;
uint32_t target_idx = offset / cluster_size;
uint32_t curr_cluster = start_cluster;
for (uint32_t i = 0; i < target_idx; i++) {
uint32_t next = read_fat_entry(vol, curr_cluster);
if (next >= (vol->is_fat32 ? 0x0FFFFFF8 : 0xFFF8)) {
return curr_cluster;
}
curr_cluster = next;
}
return curr_cluster;
}
/* ── VFS Mappings ───────────────────────────────────────────────────────── */
static int syn_fat_vfs_open(SYN_VfsFile *file, const char *path, int flags, void *fs_data)
{
(void)fs_data;
char fat_name[11];
path_to_fat_name(path, fat_name);
int f_idx = -1;
for (int i = 0; i < SYN_VFS_MAX_OPEN_FILES; i++) {
if (!g_fat_files[i].used) {
f_idx = i;
break;
}
}
if (f_idx < 0)
return -1;
DirEntryLoc loc;
bool exists = scan_root_dir(&g_vol, fat_name, &loc, false);
int access_mode = flags & 0x03;
if (!exists) {
if (!(flags & SYN_O_CREAT)) {
return -2;
}
DirEntryLoc empty_slot;
if (!scan_root_dir(&g_vol, fat_name, &empty_slot, true)) {
return -3;
}
uint32_t free_cluster = find_free_cluster(&g_vol);
if (free_cluster == 0) {
return -4;
}
if (!write_fat_entry(&g_vol, free_cluster, 0x0FFFFFFF)) {
return -5;
}
uint8_t sec_buf[512];
if (syn_sd_read(&g_sd, empty_slot.sector, sec_buf) != SYN_OK)
return -6;
uint8_t *entry = &sec_buf[empty_slot.offset];
memcpy(entry, fat_name, 11);
entry[11] = 0x20;
memset(entry + 12, 0, 8);
syn_poke_u16_le((uint16_t)(free_cluster >> 16), entry, 20);
syn_poke_u16_le((uint16_t)(free_cluster & 0xFFFFU), entry, 26);
syn_poke_u32_le(0, entry, 28);
if (syn_sd_write(&g_sd, empty_slot.sector, sec_buf) != SYN_OK)
return -7;
loc.sector = empty_slot.sector;
loc.offset = empty_slot.offset;
loc.start_cluster = free_cluster;
loc.file_size = 0;
}
g_fat_files[f_idx].used = true;
memcpy(g_fat_files[f_idx].name, fat_name, 11);
g_fat_files[f_idx].start_cluster = loc.start_cluster;
g_fat_files[f_idx].size = loc.file_size;
g_fat_files[f_idx].offset = 0;
g_fat_files[f_idx].dir_sector = loc.sector;
g_fat_files[f_idx].dir_offset = loc.offset;
g_fat_files[f_idx].mode = access_mode;
file->fs_file = &g_fat_files[f_idx];
if (flags & SYN_O_TRUNC) {
g_fat_files[f_idx].size = 0;
uint8_t sec_buf[512];
if (syn_sd_read(&g_sd, loc.sector, sec_buf) == SYN_OK) {
memset(&sec_buf[loc.offset + 28], 0, 4);
syn_sd_write(&g_sd, loc.sector, sec_buf);
}
}
if (flags & SYN_O_APPEND) {
g_fat_files[f_idx].offset = g_fat_files[f_idx].size;
}
return 0;
}
static int syn_fat_vfs_close(SYN_VfsFile *file)
{
SYN_FatFileCtx *ctx = (SYN_FatFileCtx *)file->fs_file;
if (!ctx)
return -1;
ctx->used = false;
file->fs_file = NULL;
return 0;
}
static int syn_fat_vfs_read(SYN_VfsFile *file, void *buf, size_t len)
{
SYN_FatFileCtx *ctx = (SYN_FatFileCtx *)file->fs_file;
if (!ctx)
return -1;
if (ctx->offset >= ctx->size)
return 0;
if (ctx->offset + len > ctx->size) {
len = ctx->size - ctx->offset;
}
uint8_t sec_buf[512];
uint32_t bytes_read = 0;
uint8_t *dest = (uint8_t *)buf;
uint32_t cluster_size = g_vol.sectors_per_cluster * 512;
while (bytes_read < len) {
uint32_t curr_cluster = get_cluster_for_offset(&g_vol, ctx->start_cluster, ctx->offset);
uint32_t byte_in_cluster = ctx->offset % cluster_size;
uint32_t sector_in_cluster = byte_in_cluster / 512;
uint32_t byte_in_sector = byte_in_cluster % 512;
uint32_t sector = cluster_to_sector(&g_vol, curr_cluster) + sector_in_cluster;
if (syn_sd_read(&g_sd, sector, sec_buf) != SYN_OK) {
return -2;
}
uint32_t to_copy = 512 - byte_in_sector;
if (to_copy > (len - bytes_read)) {
to_copy = len - bytes_read;
}
memcpy(dest + bytes_read, sec_buf + byte_in_sector, to_copy);
bytes_read += to_copy;
ctx->offset += to_copy;
}
return (int)bytes_read;
}
static int syn_fat_vfs_write(SYN_VfsFile *file, const void *buf, size_t len)
{
SYN_FatFileCtx *ctx = (SYN_FatFileCtx *)file->fs_file;
if (!ctx)
return -1;
uint8_t sec_buf[512];
uint32_t bytes_written = 0;
const uint8_t *src = (const uint8_t *)buf;
uint32_t cluster_size = g_vol.sectors_per_cluster * 512;
while (bytes_written < len) {
uint32_t target_idx = ctx->offset / cluster_size;
uint32_t curr_cluster = ctx->start_cluster;
for (uint32_t i = 0; i < target_idx; i++) {
uint32_t next = read_fat_entry(&g_vol, curr_cluster);
if (next >= (g_vol.is_fat32 ? 0x0FFFFFF8 : 0xFFF8)) {
uint32_t new_cluster = find_free_cluster(&g_vol);
if (new_cluster == 0)
return -2;
if (!write_fat_entry(&g_vol, curr_cluster, new_cluster))
return -3;
if (!write_fat_entry(&g_vol, new_cluster, 0x0FFFFFFF))
return -4;
curr_cluster = new_cluster;
} else {
curr_cluster = next;
}
}
uint32_t byte_in_cluster = ctx->offset % cluster_size;
uint32_t sector_in_cluster = byte_in_cluster / 512;
uint32_t byte_in_sector = byte_in_cluster % 512;
uint32_t sector = cluster_to_sector(&g_vol, curr_cluster) + sector_in_cluster;
if (byte_in_sector != 0 || (len - bytes_written) < 512) {
if (syn_sd_read(&g_sd, sector, sec_buf) != SYN_OK) {
return -5;
}
}
uint32_t to_write = 512 - byte_in_sector;
if (to_write > (len - bytes_written)) {
to_write = len - bytes_written;
}
memcpy(sec_buf + byte_in_sector, src + bytes_written, to_write);
if (syn_sd_write(&g_sd, sector, sec_buf) != SYN_OK) {
return -6;
}
bytes_written += to_write;
ctx->offset += to_write;
if (ctx->offset > ctx->size) {
ctx->size = ctx->offset;
}
}
uint8_t dir_buf[512];
if (syn_sd_read(&g_sd, ctx->dir_sector, dir_buf) == SYN_OK) {
syn_poke_u32_le(ctx->size, dir_buf, ctx->dir_offset + 28);
syn_sd_write(&g_sd, ctx->dir_sector, dir_buf);
}
return (int)bytes_written;
}
static int32_t syn_fat_vfs_seek(SYN_VfsFile *file, int32_t offset, int whence)
{
SYN_FatFileCtx *ctx = (SYN_FatFileCtx *)file->fs_file;
if (!ctx)
return -1;
int32_t target_offset = 0;
if (whence == SYN_SEEK_SET) {
target_offset = offset;
} else if (whence == SYN_SEEK_CUR) {
target_offset = (int32_t)ctx->offset + offset;
} else if (whence == SYN_SEEK_END) {
target_offset = (int32_t)ctx->size + offset;
} else {
return -2;
}
if (target_offset < 0)
target_offset = 0;
if ((uint32_t)target_offset > ctx->size)
target_offset = ctx->size;
ctx->offset = (uint32_t)target_offset;
return (int32_t)ctx->offset;
}
static int32_t syn_fat_vfs_tell(SYN_VfsFile *file)
{
const SYN_FatFileCtx *ctx = (const SYN_FatFileCtx *)file->fs_file;
if (!ctx)
return -1;
return (int32_t)ctx->offset;
}
static int syn_fat_vfs_unlink(const char *path, void *fs_data)
{
(void)fs_data;
char fat_name[11];
path_to_fat_name(path, fat_name);
DirEntryLoc loc;
if (!scan_root_dir(&g_vol, fat_name, &loc, false)) {
return -1;
}
uint8_t sec_buf[512];
if (syn_sd_read(&g_sd, loc.sector, sec_buf) != SYN_OK)
return -2;
sec_buf[loc.offset] = 0xE5;
if (syn_sd_write(&g_sd, loc.sector, sec_buf) != SYN_OK)
return -3;
uint32_t cluster = loc.start_cluster;
uint32_t eof_val = g_vol.is_fat32 ? 0x0FFFFFF8 : 0xFFF8;
while (cluster >= 2 && cluster < eof_val) {
uint32_t next = read_fat_entry(&g_vol, cluster);
write_fat_entry(&g_vol, cluster, 0);
cluster = next;
}
return 0;
}
static const SYN_VfsOps g_fat_vfs_ops = {.open = syn_fat_vfs_open,
.close = syn_fat_vfs_close,
.read = syn_fat_vfs_read,
.write = syn_fat_vfs_write,
.seek = syn_fat_vfs_seek,
.tell = syn_fat_vfs_tell,
.unlink = syn_fat_vfs_unlink,
.mkdir = NULL,
.opendir = NULL,
.readdir = NULL,
.closedir = NULL};
const SYN_VfsOps *syn_fat_get_ops(void)
{
return &g_fat_vfs_ops;
}
SYN_Status syn_fat_init(uint8_t spi_bus, SYN_GPIO_Pin cs)
{
if (syn_sd_init(&g_sd, spi_bus, cs) != SYN_OK) {
SYN_LOG_E(TAG, "%s", "syn_sd_init failed");
return SYN_ERROR;
}
uint8_t sector0[512];
if (syn_sd_read(&g_sd, 0, sector0) != SYN_OK) {
SYN_LOG_E(TAG, "%s", "Failed to read Sector 0");
return SYN_ERROR;
}
uint32_t part_start = find_partition_start(sector0);
uint8_t bpb_buf[512];
if (part_start != 0) {
SYN_LOG_I(TAG, "FAT partition found starting at sector %u", (unsigned)part_start);
if (syn_sd_read(&g_sd, part_start, bpb_buf) != SYN_OK) {
SYN_LOG_E(TAG, "%s", "Failed to read partition Boot Sector");
return SYN_ERROR;
}
} else {
memcpy(bpb_buf, sector0, 512);
}
if (!fat_parse_bpb(&g_vol, bpb_buf, part_start)) {
SYN_LOG_E(TAG, "%s", "Invalid Boot Sector BPB format");
return SYN_ERROR;
}
SYN_LOG_I(TAG, "Mounted %s filesystem: sectors/cluster=%u reserved=%u",
g_vol.is_fat32 ? "FAT32" : "FAT16", (unsigned)g_vol.sectors_per_cluster,
(unsigned)g_vol.reserved_sectors);
memset(g_fat_files, 0, sizeof(g_fat_files));
if (syn_vfs_mount("/sd", &g_fat_vfs_ops, NULL) != SYN_OK) {
SYN_LOG_E(TAG, "%s", "VFS mount /sd failed");
return SYN_ERROR;
}
return SYN_OK;
}
#endif /* SYN_USE_FAT */