// This file implements a filesystem with a minimum block size of 256 bytes. The // maximum number of files depends on the block size. The default 1KB block size // will give us 32-64 files depending on the size of MEM_CART. In case we want // to use a block size of 512 bytes, we will have up to 128 file available. // Blocks of 256 bytes will give us the maximum of 255 files available, since // a block index of 0xFF will be considered as a null block. // A fileblock of 1KB give us a maximum of 64 files. #define FILE_BLOCK_SIZE KB(1) #define FILE_MAX_FILES 64 #define FILE_N_BLOCKS 62 // With this file name size sizeof(FileIndex) will be 32 bytes. 32 * 64 files // give us 2KB spent on file index that we can't use for data (so maximum of 62 // files without accounting for the block index). #define FILE_NAME_SIZE 30 #define FILE_INDEX_NUM 62 // Since we are reserving the first 2K bytes for the filesystem, we have 60 // blocks available for writing data. If you were to change the previous // parameters, you *must* recalculate the initial block start location. #define FILE_DATA_START KB(2) // We must write to the SRAM using the 8bit bus. #define SRAM ((vu8*)(MEM_CART)) // Special filesystem constants. enum { FS_INIT_PATTERN = 0xBA, FS_NULL = 0xFF }; typedef struct FileBlock { // Size used in the current block (in bytes). Should be smaller than: // FILE_BLOCK_SIZE - sizeof(FileBlock) u16 size; // The index for the next block. Set to FS_NULL if there is none. u8 next_block; u8 prev_block; } FileBlock; typedef struct FileIndex { // File name. char name[FILE_NAME_SIZE + 1]; // Index to the first block of this file. If set to FS_NULL this file // has not yet been written to. u8 first_block; } FileIndex; // The filesystem header. typedef struct FileSystem { // The first byte of the SRAM can become corrupted in some situations, like // changing cartridges for example. u8 blank; // If the filesystem exists, this will be set to FS_INIT_PATTERN. u8 initialized; // Number of blocks in use. u8 busy_blocks; // Number of files currently existing in the filesystem. u8 num_files; // This stores a bitmap pattern to keep track of the blocks in use by the // filesystem. The first byte maps the first 8 blocks and so on. u8 used_blocks[FILE_MAX_FILES / 8]; // The list of possible file indexes. FileIndex files[FILE_INDEX_NUM]; } FileSystem; EWRAM_BSS static FileSystem filesystem; void _fs_read(u8 *dst, size_t pos, size_t n_bytes) { for (size_t i = 0; i < n_bytes; ++i) { dst[i] = SRAM[pos + i]; } } void _fs_write(u8 *src, size_t pos, size_t n_bytes) { for (size_t i = 0; i < n_bytes; ++i) { SRAM[pos + i] = src[i]; } } void fs_init(void) { // Load filesystem if existing. _fs_read(&filesystem, 0, sizeof(FileSystem)); if (filesystem.initialized != FS_INIT_PATTERN) { // Clear SRAM. for (size_t i = 0; i < KB(64) / 8; ++i) { SRAM[i] = 0x00; } // Initialize block headers. FileBlock block = { .size = 0, .next_block = FS_NULL, .prev_block = FS_NULL, }; for (size_t i = 0; i < FILE_INDEX_NUM; ++i) { size_t block_pos = FILE_DATA_START + FILE_BLOCK_SIZE * i; _fs_write(&block, block_pos, sizeof(FileBlock)); } // Initialize filesystem. memset(&filesystem, 0, sizeof(FileSystem)); filesystem.initialized = FS_INIT_PATTERN; for (size_t i = 0; i < FILE_INDEX_NUM; ++i) { filesystem.files[i].first_block = FS_NULL; } // Write the FS to disk. _fs_write(&filesystem, 0, sizeof(FileSystem)); } } void inline _fs_update_filesystem_header(void) { _fs_write(&filesystem, 0, offsetof(FileSystem, files)); }; void inline _fs_update_file_index(u16 index) { _fs_write(&filesystem.files[index], offsetof(FileSystem, files) + index * sizeof(FileIndex), sizeof(FileIndex)); } typedef enum { FS_OPEN_READ, FS_OPEN_WRITE, FS_OPEN_APPEND, } OpenMode; typedef struct File { u8 index; u16 cur; OpenMode mode; } File; File fs_open_file(char *name, OpenMode mode) { // Try to find an existing file. for (size_t i = 0; i < filesystem.num_files; ++i) { // TODO: Replace strcmp with vectorized fixed size char comparison. if (strcmp(name, filesystem.files[i].name) == 0) { return (File){i, 0, mode}; } } if (mode == FS_OPEN_READ) { return (File){FS_NULL, 0, mode}; } // Create a new file if there is space. if (filesystem.num_files < FILE_INDEX_NUM) { size_t index = filesystem.num_files++; size_t k = 0; while(*name) { filesystem.files[index].name[k++] = *name++; } // Update file index and filesystem on SRAM. _fs_update_file_index(index); _fs_update_filesystem_header(); return (File){index, 0, mode}; } return (File){FS_NULL, 0, mode}; } u8 _fs_init_new_block(void) { // Find free block. u8 block_index = 0; for (size_t j = 0; j < LEN(filesystem.used_blocks); ++j) { for (size_t i = 0; i < 8; ++i, block_index++) { u8 blk = (filesystem.used_blocks[j] >> i) & 0x1; if (blk == 0) { // Initialize the block. filesystem.busy_blocks++; filesystem.used_blocks[j] |= (1 << i); _fs_update_filesystem_header(); return block_index; } } } return FS_NULL; } #include "text.h" // Recursively free blocks starting at block_id. To improve performance, the // filesystem header is updated in memory but not written to disk. It is // responsability of the caller to perform the filesystem update. void _fs_free_blocks(u8 block_id) { if (block_id == FS_NULL) { return; } // Read block. FileBlock block; size_t block_pos = FILE_DATA_START + FILE_BLOCK_SIZE * block_id; _fs_read(&block, block_pos, sizeof(FileBlock)); // Update block. u8 next_block = block.next_block; block = (FileBlock){ .size = 0, .next_block = FS_NULL, .prev_block = FS_NULL, }; _fs_write(&block, block_pos, sizeof(FileBlock)); // Update dirty and busy blocks. filesystem.busy_blocks--; filesystem.used_blocks[block_id / 8] &= ~(1 << (block_id % 8)); _fs_free_blocks(next_block); } // Write to block as a new file. void _fs_write_to_block(u8 *src, size_t n_bytes, u8 block_id, u8 prev_block) { // Read initial block. FileBlock block; size_t block_pos = FILE_DATA_START + FILE_BLOCK_SIZE * block_id; _fs_read(&block, block_pos, sizeof(FileBlock)); u16 block_capacity = (FILE_BLOCK_SIZE - sizeof(FileBlock)); // Write capacity. u16 block_bytes = MIN(block_capacity, n_bytes); _fs_write(src, block_pos + sizeof(FileBlock), block_bytes); if (n_bytes > block_capacity) { if (block.next_block == FS_NULL) { // Find new available block and initialize it. block.next_block = _fs_init_new_block(); } _fs_write_to_block( src + block_capacity, n_bytes - block_capacity, block.next_block, block_id); } else if (block.next_block != FS_NULL){ // Recursively free unused blocks. _fs_free_blocks(block.next_block); _fs_update_filesystem_header(); block.next_block = FS_NULL; } // Update block header. if (prev_block != FS_NULL) { block.prev_block = prev_block; } block.size = block_bytes; _fs_write(&block, block_pos, sizeof(FileBlock)); } // void // _fs_append_to_block(u8 *src, size_t n_bytes, u8 block_id, u8 prev_block) { // // Read initial block. // FileBlock block; // size_t block_pos = FILE_DATA_START + FILE_BLOCK_SIZE * block_id; // _fs_read(&block, block_pos, sizeof(FileBlock)); // u16 block_capacity = (FILE_BLOCK_SIZE - sizeof(FileBlock)) - block.size; // // Write capacity. // u16 block_bytes = MIN(block_capacity, n_bytes); // _fs_write(src, block_pos + sizeof(FileBlock), block_bytes); // // txt_printf("cap: %d\n", block_capacity); // // txt_printf("bytes: %d\n", n_bytes); // // txt_printf("id: %d\n", block_id); // if (n_bytes > block_capacity) { // if (block.next_block == FS_NULL) { // // Find new available block and initialize it. // block.next_block = _fs_init_new_block(); // // TODO: Don't forget to set the block_prev of the next block as // // this one. // } // _fs_write_to_block( // src + block_capacity, // n_bytes - block_capacity, // block.next_block, // block_id); // } // // Update block header. // if (prev_block != FS_NULL) { // block.prev_block = prev_block; // } // block.size += block_bytes; // _fs_write(&block, block_pos, sizeof(FileBlock)); // } size_t fs_write(u8 *src, size_t n_bytes, u16 offset, bool append, File *file) { FileIndex *file_idx = &filesystem.files[file->index]; // TODO: Account for offset. // If this is a new file. if (file_idx->first_block == FS_NULL) { // Check how many blocks will this write require and if we have enough // available. u16 blocks_required = n_bytes / (FILE_BLOCK_SIZE - sizeof(FileBlock)); u16 blocks_available = FILE_N_BLOCKS - filesystem.busy_blocks; if (blocks_required > blocks_available) { return 0; } // Find the first available block. u8 block_id = _fs_init_new_block(); file_idx->first_block = block_id; // Update file index on SRAM. _fs_update_file_index(file->index); } else { // TODO: Check how many blocks will this write require and if we have // enough available. } // txt_printf("id: %d", file_idx->first_block); // Write to block. _fs_write_to_block(src, n_bytes, file_idx->first_block, FS_NULL); // // Update file index. // if (append) { // if (offset + n_bytes > file->size) { // file->size = offset + n_bytes; // } // } else { // file->size = offset + n_bytes; // } // _fs_write(file, FILE_INDEX_OFFSET + file_index * sizeof(File), sizeof(File)); return n_bytes; } size_t fs_read(u8 *dst, size_t n_bytes, u16 offset, File *file) { // File *file = &filesystem.files[file_index]; // // Check if the offset is within limits. // if (file->size == 0 || offset >= file->size - 1) { // return 0; // } // // Read as much as we can. // if (offset + n_bytes > file->size) { // n_bytes = file->size - offset; // } // // Copy n_bytes to destination. // _fs_read(dst, FILE_DATA_OFFSET + FILE_MAX_SIZE * file_index + offset, n_bytes); return n_bytes; }