forked from cloudyourcar/ringfs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ringfs.c
473 lines (382 loc) · 13.8 KB
/
ringfs.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
/*
* Copyright © 2014 Kosma Moczek <kosma@cloudyourcar.com>
* This program is free software. It comes without any warranty, to the extent
* permitted by applicable law. You can redistribute it and/or modify it under
* the terms of the Do What The Fuck You Want To Public License, Version 2, as
* published by Sam Hocevar. See the COPYING file for more details.
*/
/**
* @defgroup ringfs_impl RingFS implementation
* @details
*
* @{
*/
#include <ringfs.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stddef.h>
/**
* @defgroup sector
* @{
*/
enum sector_status {
SECTOR_ERASED = 0xFFFFFFFF, /**< Default state after NOR flash erase. */
SECTOR_FREE = 0xFFFFFF00, /**< Sector erased. */
SECTOR_IN_USE = 0xFFFF0000, /**< Sector contains valid data. */
SECTOR_ERASING = 0xFF000000, /**< Sector should be erased. */
SECTOR_FORMATTING = 0x00000000, /**< The entire partition is being formatted. */
};
struct sector_header {
uint32_t status;
uint32_t version;
};
static int _sector_address(struct ringfs *fs, int sector_offset)
{
return (fs->flash->sector_offset + sector_offset) * fs->flash->sector_size;
}
static int _sector_get_status(struct ringfs *fs, int sector, uint32_t *status)
{
return fs->flash->read(fs->flash,
_sector_address(fs, sector) + offsetof(struct sector_header, status),
status, sizeof(*status));
}
static int _sector_set_status(struct ringfs *fs, int sector, uint32_t status)
{
return fs->flash->program(fs->flash,
_sector_address(fs, sector) + offsetof(struct sector_header, status),
&status, sizeof(status));
}
static int _sector_free(struct ringfs *fs, int sector)
{
int sector_addr = _sector_address(fs, sector);
_sector_set_status(fs, sector, SECTOR_ERASING);
fs->flash->sector_erase(fs->flash, sector_addr);
fs->flash->program(fs->flash,
sector_addr + offsetof(struct sector_header, version),
&fs->version, sizeof(fs->version));
_sector_set_status(fs, sector, SECTOR_FREE);
return 0;
}
/**
* @}
* @defgroup slot
* @{
*/
enum slot_status {
SLOT_ERASED = 0xFFFFFFFF, /**< Default state after NOR flash erase. */
SLOT_RESERVED = 0xFFFFFF00, /**< Write started but not yet committed. */
SLOT_VALID = 0xFFFF0000, /**< Write committed, slot contains valid data. */
SLOT_GARBAGE = 0xFF000000, /**< Slot contents discarded and no longer valid. */
};
struct slot_header {
uint32_t status;
};
static int _slot_address(struct ringfs *fs, struct ringfs_loc *loc)
{
return _sector_address(fs, loc->sector) +
sizeof(struct sector_header) +
(sizeof(struct slot_header) + fs->object_size) * loc->slot;
}
static int _slot_get_status(struct ringfs *fs, struct ringfs_loc *loc, uint32_t *status)
{
return fs->flash->read(fs->flash,
_slot_address(fs, loc) + offsetof(struct slot_header, status),
status, sizeof(*status));
}
static int _slot_set_status(struct ringfs *fs, struct ringfs_loc *loc, uint32_t status)
{
return fs->flash->program(fs->flash,
_slot_address(fs, loc) + offsetof(struct slot_header, status),
&status, sizeof(status));
}
/**
* @}
* @defgroup loc
* @{
*/
static bool _loc_equal(struct ringfs_loc *a, struct ringfs_loc *b)
{
return (a->sector == b->sector) && (a->slot == b->slot);
}
/** Advance a location to the beginning of the next sector. */
static void _loc_advance_sector(struct ringfs *fs, struct ringfs_loc *loc)
{
loc->slot = 0;
loc->sector++;
if (loc->sector >= fs->flash->sector_count)
loc->sector = 0;
}
/** Advance a location to the next slot, advancing the sector too if needed. */
static void _loc_advance_slot(struct ringfs *fs, struct ringfs_loc *loc)
{
loc->slot++;
if (loc->slot >= fs->slots_per_sector)
_loc_advance_sector(fs, loc);
}
/**
* @}
*/
/* And here we go. */
int ringfs_init(struct ringfs *fs, struct ringfs_flash_partition *flash, uint32_t version, int object_size)
{
/* Copy arguments to instance. */
fs->flash = flash;
fs->version = version;
fs->object_size = object_size;
/* Precalculate commonly used values. */
fs->slots_per_sector = (fs->flash->sector_size - sizeof(struct sector_header)) /
(sizeof(struct slot_header) + fs->object_size);
return 0;
}
int ringfs_format(struct ringfs *fs)
{
/* Mark all sectors to prevent half-erased filesystems. */
for (int sector=0; sector<fs->flash->sector_count; sector++)
_sector_set_status(fs, sector, SECTOR_FORMATTING);
/* Erase, update version, mark as free. */
for (int sector=0; sector<fs->flash->sector_count; sector++)
_sector_free(fs, sector);
/* Start reading & writing at the first sector. */
fs->read.sector = 0;
fs->read.slot = 0;
fs->write.sector = 0;
fs->write.slot = 0;
fs->cursor.sector = 0;
fs->cursor.slot = 0;
return 0;
}
int ringfs_scan(struct ringfs *fs)
{
uint32_t previous_sector_status = SECTOR_FREE;
/* The read sector is the first IN_USE sector *after* a FREE sector
* (or the first one). */
int read_sector = 0;
/* The write sector is the last IN_USE sector *before* a FREE sector
* (or the last one). */
int write_sector = fs->flash->sector_count - 1;
/* There must be at least one FREE sector available at all times. */
bool free_seen = false;
/* If there's no IN_USE sector, we start at the first one. */
bool used_seen = false;
/* Iterate over sectors. */
for (int sector=0; sector<fs->flash->sector_count; sector++) {
int addr = _sector_address(fs, sector);
/* Read sector header. */
struct sector_header header;
fs->flash->read(fs->flash, addr, &header, sizeof(header));
/* Detect partially-formatted partitions. */
if (header.status == SECTOR_FORMATTING) {
printf("ringfs_scan: partially formatted partition\r\n");
return -1;
}
/* Detect and fix partially erased sectors. */
if (header.status == SECTOR_ERASING || header.status == SECTOR_ERASED) {
_sector_free(fs, addr);
header.status = SECTOR_FREE;
}
/* Detect corrupted sectors. */
if (header.status != SECTOR_FREE && header.status != SECTOR_IN_USE) {
printf("ringfs_scan: corrupted sector %d\r\n", sector);
return -1;
}
/* Detect obsolete versions. We can't do this earlier because the version
* could have been invalid due to a partial erase. */
if (header.version != fs->version) {
printf("ringfs_scan: incompatible version 0x%08"PRIx32"\r\n", header.version);
return -1;
}
/* Record the presence of a FREE sector. */
if (header.status == SECTOR_FREE)
free_seen = true;
/* Record the presence of a IN_USE sector. */
if (header.status == SECTOR_IN_USE)
used_seen = true;
/* Update read & write sectors according to the above rules. */
if (header.status == SECTOR_IN_USE && previous_sector_status == SECTOR_FREE)
read_sector = sector;
if (header.status == SECTOR_FREE && previous_sector_status == SECTOR_IN_USE)
write_sector = sector-1;
previous_sector_status = header.status;
}
/* Detect the lack of a FREE sector. */
if (!free_seen) {
printf("ringfs_scan: invariant violated: no FREE sector found\r\n");
return -1;
}
/* Start writing at the first sector if the filesystem is empty. */
if (!used_seen) {
write_sector = 0;
}
/* Scan the write sector and skip all occupied slots at the beginning. */
fs->write.sector = write_sector;
fs->write.slot = 0;
while (fs->write.sector == write_sector) {
uint32_t status;
_slot_get_status(fs, &fs->write, &status);
if (status == SLOT_ERASED)
break;
_loc_advance_slot(fs, &fs->write);
}
/* If the sector was full, we're at the beginning of a FREE sector now. */
/* Position the read head at the start of the first IN_USE sector, then skip
* over garbage/invalid slots until something of value is found or we reach
* the write head which means there's no data. */
fs->read.sector = read_sector;
fs->read.slot = 0;
while (!_loc_equal(&fs->read, &fs->write)) {
uint32_t status;
_slot_get_status(fs, &fs->read, &status);
if (status == SLOT_VALID)
break;
_loc_advance_slot(fs, &fs->read);
}
/* Move the read cursor to the read head position. */
fs->cursor = fs->read;
return 0;
}
int ringfs_capacity(struct ringfs *fs)
{
return fs->slots_per_sector * (fs->flash->sector_count - 1);
}
int ringfs_count_estimate(struct ringfs *fs)
{
int sector_diff = (fs->write.sector - fs->read.sector + fs->flash->sector_count) %
fs->flash->sector_count;
return sector_diff * fs->slots_per_sector + fs->write.slot - fs->read.slot;
}
int ringfs_count_exact(struct ringfs *fs)
{
int count = 0;
/* Use a temporary loc for iteration. */
struct ringfs_loc loc = fs->read;
while (!_loc_equal(&loc, &fs->write)) {
uint32_t status;
_slot_get_status(fs, &loc, &status);
if (status == SLOT_VALID)
count++;
_loc_advance_slot(fs, &loc);
}
return count;
}
int ringfs_append(struct ringfs *fs, const void *object)
{
uint32_t status;
/*
* There are three sectors involved in appending a value:
* - the sector where the append happens: it has to be writable
* - the next sector: it must be free (invariant)
* - the next-next sector: read & cursor heads are moved there if needed
*/
/* Make sure the next sector is free. */
int next_sector = (fs->write.sector+1) % fs->flash->sector_count;
_sector_get_status(fs, next_sector, &status);
if (status != SECTOR_FREE) {
/* Next sector must be freed. But first... */
/* Move the read & cursor heads out of the way. */
if (fs->read.sector == next_sector)
_loc_advance_sector(fs, &fs->read);
if (fs->cursor.sector == next_sector)
_loc_advance_sector(fs, &fs->cursor);
/* Free the next sector. */
_sector_free(fs, next_sector);
}
/* Now we can make sure the current write sector is writable. */
_sector_get_status(fs, fs->write.sector, &status);
if (status == SECTOR_FREE) {
/* Free sector. Mark as used. */
_sector_set_status(fs, fs->write.sector, SECTOR_IN_USE);
} else if (status != SECTOR_IN_USE) {
printf("ringfs_append: corrupted filesystem\r\n");
return -1;
}
/* Preallocate slot. */
_slot_set_status(fs, &fs->write, SLOT_RESERVED);
/* Write object. */
fs->flash->program(fs->flash,
_slot_address(fs, &fs->write) + sizeof(struct slot_header),
object, fs->object_size);
/* Commit write. */
_slot_set_status(fs, &fs->write, SLOT_VALID);
/* Advance the write head. */
_loc_advance_slot(fs, &fs->write);
return 0;
}
int ringfs_fetch(struct ringfs *fs, void *object)
{
/* Advance forward in search of a valid slot. */
while (!_loc_equal(&fs->cursor, &fs->write)) {
uint32_t status;
_slot_get_status(fs, &fs->cursor, &status);
if (status == SLOT_VALID) {
fs->flash->read(fs->flash,
_slot_address(fs, &fs->cursor) + sizeof(struct slot_header),
object, fs->object_size);
_loc_advance_slot(fs, &fs->cursor);
return 0;
}
_loc_advance_slot(fs, &fs->cursor);
}
return -1;
}
int ringfs_discard(struct ringfs *fs)
{
while (!_loc_equal(&fs->read, &fs->cursor)) {
_slot_set_status(fs, &fs->read, SLOT_GARBAGE);
_loc_advance_slot(fs, &fs->read);
}
return 0;
}
int ringfs_item_discard(struct ringfs *fs)
{
_slot_set_status(fs, &fs->read, SLOT_GARBAGE);
_loc_advance_slot(fs, &fs->read);
return 0;
}
int ringfs_rewind(struct ringfs *fs)
{
fs->cursor = fs->read;
return 0;
}
void ringfs_dump(FILE *stream, struct ringfs *fs)
{
const char *description;
fprintf(stream, "RingFS read: {%d,%d} cursor: {%d,%d} write: {%d,%d}\n",
fs->read.sector, fs->read.slot,
fs->cursor.sector, fs->cursor.slot,
fs->write.sector, fs->write.slot);
for (int sector=0; sector<fs->flash->sector_count; sector++) {
int addr = _sector_address(fs, sector);
/* Read sector header. */
struct sector_header header;
fs->flash->read(fs->flash, addr, &header, sizeof(header));
switch (header.status) {
case SECTOR_ERASED: description = "ERASED"; break;
case SECTOR_FREE: description = "FREE"; break;
case SECTOR_IN_USE: description = "IN_USE"; break;
case SECTOR_ERASING: description = "ERASING"; break;
case SECTOR_FORMATTING: description = "FORMATTING"; break;
default: description = "UNKNOWN"; break;
}
fprintf(stream, "[%04d] [v=0x%08"PRIx32"] [%-10s] ",
sector, header.version, description);
for (int slot=0; slot<fs->slots_per_sector; slot++) {
struct ringfs_loc loc = { sector, slot };
uint32_t status;
_slot_get_status(fs, &loc, &status);
switch (status) {
case SLOT_ERASED: description = "E"; break;
case SLOT_RESERVED: description = "R"; break;
case SLOT_VALID: description = "V"; break;
case SLOT_GARBAGE: description = "G"; break;
default: description = "?"; break;
}
fprintf(stream, "%s", description);
}
fprintf(stream, "\n");
}
fflush(stream);
}
/**
* @}
*/
/* vim: set ts=4 sw=4 et: */