aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBad Diode <bd@badd10de.dev>2023-05-28 11:06:04 +0200
committerBad Diode <bd@badd10de.dev>2023-05-28 11:06:04 +0200
commit5024c6631ab583c0b375f0002e2ad6d6373d84ee (patch)
tree69c11c0f2562ceb740545d2c506a335f3935339f
parenta3b2141540d74456689ddfaac44c4f83f995c6a7 (diff)
downloadstepper-5024c6631ab583c0b375f0002e2ad6d6373d84ee.tar.gz
stepper-5024c6631ab583c0b375f0002e2ad6d6373d84ee.zip
Fix save data corruption if no save existed
-rw-r--r--src/filesystem.c409
-rw-r--r--src/filesystem.h36
-rw-r--r--src/main.c1
-rw-r--r--src/save.c5
-rw-r--r--src/sequencer.c8
5 files changed, 10 insertions, 449 deletions
diff --git a/src/filesystem.c b/src/filesystem.c
deleted file mode 100644
index 00c0605..0000000
--- a/src/filesystem.c
+++ /dev/null
@@ -1,409 +0,0 @@
1/*
2Copyright (c) 2021 Bad Diode
3
4Permission to use, copy, modify, and distribute this software for any
5purpose with or without fee is hereby granted, provided that the above
6copyright notice and this permission notice appear in all copies.
7
8THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9WITH REGARD TO THIS SOFTWARE.
10*/
11
12#include <string.h>
13
14#include "filesystem.h"
15
16// This file implements a filesystem with a minimum block size of 256 bytes. The
17// maximum number of files depends on the block size. The default 1KB block size
18// will give us 32-64 files depending on the size of MEM_CART. In case we want
19// to use a block size of 512 bytes, we will have up to 128 file available.
20// Blocks of 256 bytes will give us the maximum of 255 files available, since
21// a block index of 0xFF will be considered as a null block.
22
23// A fileblock of 1KB give us a maximum of 64 files.
24#define FILE_BLOCK_SIZE KB(1)
25#define FILE_MAX_FILES 64
26#define FILE_N_BLOCKS 62
27
28// With this file name size sizeof(FileIndex) will be 32 bytes. 32 * 64 files
29// give us 2KB spent on file index that we can't use for data (so maximum of 62
30// files without accounting for the block index).
31#define FILE_NAME_SIZE 30
32#define FILE_INDEX_NUM 62
33
34// Since we are reserving the first 2K bytes for the filesystem, we have 60
35// blocks available for writing data. If you were to change the previous
36// parameters, you *must* recalculate the initial block start location.
37#define FILE_DATA_START KB(2)
38
39// We must write to the SRAM using the 8bit bus.
40#define SRAM ((vu8*)(MEM_CART))
41
42// Special filesystem constants.
43enum { FS_INIT_PATTERN = 0xBA, FS_NULL = 0xFF };
44
45typedef struct FileBlock {
46 // Size used in the current block (in bytes). Should be smaller than:
47 // FILE_BLOCK_SIZE - sizeof(FileBlock)
48 u16 size;
49 // The index for the next block. Set to FS_NULL if there is none.
50 u8 next_block;
51 u8 prev_block;
52} FileBlock;
53
54typedef struct FileIndex {
55 // File name.
56 char name[FILE_NAME_SIZE + 1];
57 // Index to the first block of this file. If set to FS_NULL this file
58 // has not yet been written to.
59 u8 first_block;
60} FileIndex;
61
62// The filesystem header.
63typedef struct FileSystem {
64 // The first byte of the SRAM can become corrupted in some situations, like
65 // changing cartridges for example.
66 u8 blank;
67 // If the filesystem exists, this will be set to FS_INIT_PATTERN.
68 u8 initialized;
69 // Number of blocks in use.
70 u8 busy_blocks;
71 // Number of files currently existing in the filesystem.
72 u8 num_files;
73 // This stores a bitmap pattern to keep track of the blocks in use by the
74 // filesystem. The first byte maps the first 8 blocks and so on.
75 u8 used_blocks[FILE_MAX_FILES / 8];
76 // The list of possible file indexes.
77 FileIndex files[FILE_INDEX_NUM];
78} FileSystem;
79
80#define FILE_BLOCK_CAPACITY (FILE_BLOCK_SIZE - sizeof(FileBlock))
81
82EWRAM_BSS
83static FileSystem filesystem;
84
85void
86_fs_read(u8 *dst, u16 pos, u16 n_bytes) {
87 for (size_t i = 0; i < n_bytes; ++i) {
88 dst[i] = SRAM[pos + i];
89 }
90}
91
92void
93_fs_write(u8 *src, u16 pos, u16 n_bytes) {
94 for (size_t i = 0; i < n_bytes; ++i) {
95 SRAM[pos + i] = src[i];
96 }
97}
98
99void
100fs_init(void) {
101 // Load filesystem if existing.
102 _fs_read(&filesystem, 0, sizeof(FileSystem));
103 if (filesystem.initialized != FS_INIT_PATTERN) {
104 // Clear SRAM.
105 for (size_t i = 0; i < KB(64) / 8; ++i) {
106 SRAM[i] = 0x00;
107 }
108
109 // Initialize block headers.
110 FileBlock block = {
111 .size = 0,
112 .next_block = FS_NULL,
113 .prev_block = FS_NULL,
114 };
115 for (size_t i = 0; i < FILE_INDEX_NUM; ++i) {
116 u16 block_pos = FILE_DATA_START + FILE_BLOCK_SIZE * i;
117 _fs_write(&block, block_pos, sizeof(FileBlock));
118 }
119
120 // Initialize filesystem.
121 dma_fill(&filesystem, 0, sizeof(FileSystem), 3);
122 filesystem.initialized = FS_INIT_PATTERN;
123 for (size_t i = 0; i < FILE_INDEX_NUM; ++i) {
124 filesystem.files[i].first_block = FS_NULL;
125 }
126
127 // Write the FS to disk.
128 _fs_write(&filesystem, 0, sizeof(FileSystem));
129 }
130}
131
132void
133_fs_update_filesystem_header(void) {
134 _fs_write(&filesystem, 0, offsetof(FileSystem, files));
135}
136
137void
138_fs_update_file_index(u16 index) {
139 _fs_write(&filesystem.files[index],
140 offsetof(FileSystem, files) + index * sizeof(FileIndex),
141 sizeof(FileIndex));
142}
143
144File
145fs_open_file(char *name, OpenMode mode) {
146 // Try to find an existing file.
147 for (size_t i = 0; i < filesystem.num_files; ++i) {
148 // TODO: Replace strcmp with vectorized fixed size char comparison.
149 if (strcmp(name, filesystem.files[i].name) == 0) {
150 return (File){i, 0, mode};
151 }
152 }
153
154 // If read only.
155 if ((mode & (FS_OPEN_WRITE | FS_OPEN_APPEND)) == 0) {
156 return (File){FS_NULL, 0, mode};
157 }
158
159 // Create a new file if there is space.
160 if (filesystem.num_files < FILE_INDEX_NUM) {
161 u16 index = filesystem.num_files++;
162 u16 k = 0;
163 while(*name) {
164 filesystem.files[index].name[k++] = *name++;
165 }
166
167 // Update file index and filesystem on SRAM.
168 _fs_update_file_index(index);
169 _fs_update_filesystem_header();
170
171 return (File){index, 0, mode};
172 }
173 return (File){FS_NULL, 0, mode};
174}
175
176u16
177fs_file_size(File *file) {
178 u16 size = 0;
179 FileBlock block;
180 u16 blk_id = filesystem.files[file->index].first_block;
181 while (blk_id != FS_NULL) {
182 u16 block_pos = FILE_DATA_START + FILE_BLOCK_SIZE * blk_id;
183 _fs_read(&block, block_pos, sizeof(FileBlock));
184 size += block.size;
185 blk_id = block.next_block;
186 }
187 return size;
188}
189
190u8
191_fs_init_new_block(void) {
192 // Find free block.
193 u8 block_index = 0;
194 for (size_t j = 0; j < LEN(filesystem.used_blocks); ++j) {
195 for (size_t i = 0; i < 8; ++i, block_index++) {
196 u8 blk = (filesystem.used_blocks[j] >> i) & 0x1;
197 if (blk == 0) {
198 // Initialize the block.
199 filesystem.busy_blocks++;
200 filesystem.used_blocks[j] |= (1 << i);
201 _fs_update_filesystem_header();
202 return block_index;
203 }
204 }
205 }
206 return FS_NULL;
207}
208
209// Recursively free blocks starting at blk_id. To improve performance, the
210// filesystem header is updated in memory but not written to disk. It is
211// responsability of the caller to perform the filesystem update.
212void
213_fs_free_blocks(u8 blk_id) {
214 if (blk_id == FS_NULL) {
215 return;
216 }
217
218 // Read block.
219 FileBlock block;
220 u16 block_pos = FILE_DATA_START + FILE_BLOCK_SIZE * blk_id;
221 _fs_read(&block, block_pos, sizeof(FileBlock));
222
223 // Update block.
224 u8 next_block = block.next_block;
225 block = (FileBlock){
226 .size = 0,
227 .next_block = FS_NULL,
228 .prev_block = FS_NULL,
229 };
230 _fs_write(&block, block_pos, sizeof(FileBlock));
231
232 // Update dirty and busy blocks.
233 filesystem.busy_blocks--;
234 filesystem.used_blocks[blk_id / 8] &= ~(1 << (blk_id % 8));
235
236 _fs_free_blocks(next_block);
237}
238
239void
240_fs_write_to_block(u8 *src, u16 n_bytes, u16 blk_offset,
241 u8 blk_id, u8 prev_blk, bool append) {
242 // Read initial block.
243 FileBlock block;
244 u16 block_pos = FILE_DATA_START + FILE_BLOCK_SIZE * blk_id;
245 _fs_read(&block, block_pos, sizeof(FileBlock));
246 u16 block_capacity = FILE_BLOCK_CAPACITY - blk_offset;
247
248 // Write capacity.
249 u16 block_bytes = MIN(block_capacity, n_bytes);
250 _fs_write(src, block_pos + sizeof(FileBlock) + blk_offset, block_bytes);
251
252 if (n_bytes > block_capacity) {
253 if (block.next_block == FS_NULL) {
254 // Find new available block and initialize it.
255 block.next_block = _fs_init_new_block();
256 }
257 _fs_write_to_block(src + block_capacity, n_bytes - block_capacity, 0,
258 block.next_block, blk_id, append);
259 } else if (block.next_block != FS_NULL){
260 // Recursively free unused blocks.
261 _fs_free_blocks(block.next_block);
262 _fs_update_filesystem_header();
263 block.next_block = FS_NULL;
264 }
265
266 // Update block header.
267 if (prev_blk != FS_NULL) {
268 block.prev_block = prev_blk;
269 }
270 block.size = block_bytes;
271 _fs_write(&block, block_pos, sizeof(FileBlock));
272}
273
274int
275fs_seek(File *file, int offset, SeekMode mode) {
276 u16 file_size = fs_file_size(file);
277 u16 new_offset = 0;
278 switch (mode) {
279 case FS_SEEK_SET: {
280 new_offset = offset;
281 } break;
282 case FS_SEEK_CUR: {
283 new_offset = MAX((int)file->offset + offset, 0);
284 } break;
285 case FS_SEEK_END: {
286 new_offset = MAX((int)file_size - 1 + offset, 0);
287 } break;
288 }
289 if (new_offset != 0 && new_offset >= file_size) {
290 return -1;
291 }
292 file->offset = new_offset;
293 return 0;
294}
295
296u16
297fs_write(u8 *src, u16 n_bytes, File *file) {
298 if ((file->mode & (FS_OPEN_WRITE | FS_OPEN_APPEND)) == 0) {
299 return 0;
300 }
301
302 FileIndex *file_idx = &filesystem.files[file->index];
303
304 u8 blk_id = FS_NULL;
305 u8 blk_prev = FS_NULL;
306 u16 offset = file->offset;
307 if (file_idx->first_block == FS_NULL) {
308 // Check how many blocks will this write requires and if we have enough
309 // available.
310 u16 blocks_required = n_bytes / FILE_BLOCK_CAPACITY;
311 u16 blocks_available = FILE_N_BLOCKS - filesystem.busy_blocks;
312 if (blocks_required > blocks_available) {
313 return 0;
314 }
315
316 // Find the first available block.
317 blk_id = _fs_init_new_block();
318 file_idx->first_block = blk_id;
319
320 // Update file index on SRAM.
321 _fs_update_file_index(file->index);
322 } else {
323 // Check how many blocks will this write requires and if we have
324 // enough available.
325 u16 file_size = fs_file_size(file);
326 u16 blocks_in_file = file_size / FILE_BLOCK_SIZE;
327 u16 blocks_available = FILE_N_BLOCKS - filesystem.busy_blocks + blocks_in_file;
328 u16 blocks_required = (n_bytes + offset) / FILE_BLOCK_CAPACITY;
329 if (blocks_required > blocks_available) {
330 return 0;
331 }
332
333 blk_id = file_idx->first_block;
334
335 // If there is an offset find the block index and relative offset.
336 if (offset >= FILE_BLOCK_CAPACITY) {
337 u16 n_blocks_offset = offset / FILE_BLOCK_CAPACITY;
338 for (size_t i = 0; i < n_blocks_offset; ++i) {
339 FileBlock block;
340 u16 block_pos = FILE_DATA_START + FILE_BLOCK_SIZE * blk_id;
341 _fs_read(&block, block_pos, sizeof(FileBlock));
342 blk_id = block.next_block;
343 blk_prev = block.prev_block;
344 if (blk_id == FS_NULL) {
345 return 0;
346 }
347 }
348 offset = offset % FILE_BLOCK_CAPACITY;
349 }
350 }
351
352 // Write to block.
353 _fs_write_to_block(src, n_bytes, offset, blk_id, blk_prev,
354 file->mode == FS_OPEN_APPEND);
355
356 return n_bytes;
357}
358
359void
360_fs_read_from_block(u8 *dst, u16 n_bytes, u16 blk_offset, u8 blk_id) {
361 // Read initial block.
362 FileBlock block;
363 u16 block_pos = FILE_DATA_START + FILE_BLOCK_SIZE * blk_id;
364 _fs_read(&block, block_pos, sizeof(FileBlock));
365
366 u16 read_bytes = MIN(block.size - blk_offset, n_bytes);
367 _fs_read(dst, block_pos + blk_offset + sizeof(FileBlock), read_bytes);
368
369 u16 remaining_bytes = n_bytes - read_bytes;
370 if (block.next_block != FS_NULL && remaining_bytes > 0) {
371 _fs_read_from_block(dst + read_bytes, remaining_bytes, 0, block.next_block);
372 }
373}
374
375u16
376fs_read(u8 *dst, u16 n_bytes, File *file) {
377 if ((file->mode & FS_OPEN_READ) == 0) {
378 return 0;
379 }
380
381 // If there is an offset find the block index and relative offset.
382 u8 blk_id = filesystem.files[file->index].first_block;
383 u16 offset = file->offset;
384
385 // Read as much as we can from the file after the offset.
386 u16 file_size = fs_file_size(file);
387 if (offset + n_bytes >= file_size) {
388 n_bytes = file_size - offset;
389 }
390
391 if (offset >= FILE_BLOCK_CAPACITY) {
392 u16 n_blocks_offset = offset / FILE_BLOCK_CAPACITY;
393 for (size_t i = 0; i < n_blocks_offset; ++i) {
394 FileBlock block;
395 u16 block_pos = FILE_DATA_START + FILE_BLOCK_SIZE * blk_id;
396 _fs_read(&block, block_pos, sizeof(FileBlock));
397 blk_id = block.next_block;
398 if (blk_id == FS_NULL) {
399 return 0;
400 }
401 }
402 offset = offset % FILE_BLOCK_CAPACITY;
403 }
404
405 // Copy n_bytes to destination.
406 _fs_read_from_block(dst, n_bytes, offset, blk_id);
407
408 return n_bytes;
409}
diff --git a/src/filesystem.h b/src/filesystem.h
deleted file mode 100644
index 6439f83..0000000
--- a/src/filesystem.h
+++ /dev/null
@@ -1,36 +0,0 @@
1#ifndef FILESYSTEM_H
2#define FILESYSTEM_H
3
4#include "gba/gba.h"
5
6typedef enum {
7 FS_OPEN_READ = (1 << 0),
8 FS_OPEN_WRITE = (1 << 1),
9 FS_OPEN_APPEND = (1 << 2),
10} OpenMode;
11
12typedef struct File {
13 // File index offset.
14 u8 index;
15 // The offset within the file. Must always be valid, and so the File struct
16 // shouldn't be manaully modified unless we are sure we know what we are
17 // doing.
18 u16 offset;
19 // The mode of this file (read/write/append).
20 OpenMode mode;
21} File;
22
23typedef enum {
24 FS_SEEK_SET,
25 FS_SEEK_CUR,
26 FS_SEEK_END,
27} SeekMode;
28
29void fs_init(void);
30File fs_open_file(char *name, OpenMode mode);
31u16 fs_file_size(File *file);
32int fs_seek(File *file, int offset, SeekMode mode);
33u16 fs_write(u8 *src, u16 n_bytes, File *file);
34u16 fs_read(u8 *dst, u16 n_bytes, File *file);
35
36#endif // FILESYSTEM_H
diff --git a/src/main.c b/src/main.c
index abc8571..f9fa018 100644
--- a/src/main.c
+++ b/src/main.c
@@ -48,7 +48,6 @@ WITH REGARD TO THIS SOFTWARE.
48 48
49#include "gba/gba.h" 49#include "gba/gba.h"
50 50
51#include "filesystem.c"
52#include "renderer_m0.c" 51#include "renderer_m0.c"
53#include "sequencer.c" 52#include "sequencer.c"
54 53
diff --git a/src/save.c b/src/save.c
index 45a099d..1722e9d 100644
--- a/src/save.c
+++ b/src/save.c
@@ -2,9 +2,12 @@
2// Save data. 2// Save data.
3// 3//
4 4
5#define SRAM ((vu8*)(MEM_CART))
6#define SAVE_INIT_MAGIC 0xBADD10DE
7
5typedef struct Metadata { 8typedef struct Metadata {
6 u8 blank; 9 u8 blank;
7 u32 magic; 10 u32 initialized;
8 int current_bank; 11 int current_bank;
9 int current_pattern; 12 int current_pattern;
10} Metadata; 13} Metadata;
diff --git a/src/sequencer.c b/src/sequencer.c
index 6aa8eea..867e686 100644
--- a/src/sequencer.c
+++ b/src/sequencer.c
@@ -957,8 +957,12 @@ void
957sequencer_init(void) { 957sequencer_init(void) {
958 // Load the previous bank from SRAM or initialize it if needed. 958 // Load the previous bank from SRAM or initialize it if needed.
959 sram_read(&metadata, 0, sizeof(Metadata)); 959 sram_read(&metadata, 0, sizeof(Metadata));
960 if (metadata.magic != 0xbadd10de) { 960 if (metadata.initialized != SAVE_INIT_MAGIC) {
961 metadata.magic = 0xbadd10de; 961 for (size_t i = 0; i < KB(64) / 8; ++i) {
962 SRAM[i] = 0x00;
963 }
964 dma_fill(&metadata, 0, sizeof(Metadata), 3);
965 metadata.initialized = SAVE_INIT_MAGIC;
962 sram_write(&metadata, 0, sizeof(Metadata)); 966 sram_write(&metadata, 0, sizeof(Metadata));
963 save_bank(0); 967 save_bank(0);
964 save_bank(1); 968 save_bank(1);