diff options
Diffstat (limited to 'src/filesystem.c')
-rw-r--r-- | src/filesystem.c | 409 |
1 files changed, 0 insertions, 409 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 | /* | ||
2 | Copyright (c) 2021 Bad Diode | ||
3 | |||
4 | Permission to use, copy, modify, and distribute this software for any | ||
5 | purpose with or without fee is hereby granted, provided that the above | ||
6 | copyright notice and this permission notice appear in all copies. | ||
7 | |||
8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||
9 | WITH 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. | ||
43 | enum { FS_INIT_PATTERN = 0xBA, FS_NULL = 0xFF }; | ||
44 | |||
45 | typedef 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 | |||
54 | typedef 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. | ||
63 | typedef 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 | |||
82 | EWRAM_BSS | ||
83 | static FileSystem filesystem; | ||
84 | |||
85 | void | ||
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 | |||
92 | void | ||
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 | |||
99 | void | ||
100 | fs_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 | |||
132 | void | ||
133 | _fs_update_filesystem_header(void) { | ||
134 | _fs_write(&filesystem, 0, offsetof(FileSystem, files)); | ||
135 | } | ||
136 | |||
137 | void | ||
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 | |||
144 | File | ||
145 | fs_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 | |||
176 | u16 | ||
177 | fs_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 | |||
190 | u8 | ||
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. | ||
212 | void | ||
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 | |||
239 | void | ||
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 | |||
274 | int | ||
275 | fs_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 | |||
296 | u16 | ||
297 | fs_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 | |||
359 | void | ||
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 | |||
375 | u16 | ||
376 | fs_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 | } | ||