summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniël van de Giessen <daniel@dvdgiessen.nl>2023-06-29 14:17:56 +0200
committerDaniël van de Giessen <daniel@dvdgiessen.nl>2023-11-01 17:07:18 +0100
commit7ad84e0422a521fe97ce5d742df280c6b8a3ff87 (patch)
tree9fb8c6f15fc3020082761fa5385691d053c68815
parent06a7bf967c58644a0d341764994bdcc9253b4527 (diff)
lib/littlefs: Update LittleFS to v2.8.1.
Signed-off-by: Daniël van de Giessen <daniel@dvdgiessen.nl>
-rw-r--r--extmod/extmod.mk2
-rw-r--r--lib/littlefs/lfs2.c1007
-rw-r--r--lib/littlefs/lfs2.h82
-rw-r--r--lib/littlefs/lfs2_util.h10
4 files changed, 842 insertions, 259 deletions
diff --git a/extmod/extmod.mk b/extmod/extmod.mk
index 8a48a4293..c4b2c3d9c 100644
--- a/extmod/extmod.mk
+++ b/extmod/extmod.mk
@@ -106,7 +106,7 @@ SRC_THIRDPARTY_C += $(addprefix $(LITTLEFS_DIR)/,\
lfs2_util.c \
)
-$(BUILD)/$(LITTLEFS_DIR)/lfs2.o: CFLAGS += -Wno-missing-field-initializers
+$(BUILD)/$(LITTLEFS_DIR)/lfs2.o: CFLAGS += -Wno-shadow
endif
################################################################################
diff --git a/lib/littlefs/lfs2.c b/lib/littlefs/lfs2.c
index a9fcffafd..d89c42fd5 100644
--- a/lib/littlefs/lfs2.c
+++ b/lib/littlefs/lfs2.c
@@ -46,8 +46,8 @@ static int lfs2_bd_read(lfs2_t *lfs2,
lfs2_block_t block, lfs2_off_t off,
void *buffer, lfs2_size_t size) {
uint8_t *data = buffer;
- if (block >= lfs2->cfg->block_count ||
- off+size > lfs2->cfg->block_size) {
+ if (off+size > lfs2->cfg->block_size
+ || (lfs2->block_count && block >= lfs2->block_count)) {
return LFS2_ERR_CORRUPT;
}
@@ -104,7 +104,7 @@ static int lfs2_bd_read(lfs2_t *lfs2,
}
// load to cache, first condition can no longer fail
- LFS2_ASSERT(block < lfs2->cfg->block_count);
+ LFS2_ASSERT(!lfs2->block_count || block < lfs2->block_count);
rcache->block = block;
rcache->off = lfs2_aligndown(off, lfs2->cfg->read_size);
rcache->size = lfs2_min(
@@ -135,14 +135,14 @@ static int lfs2_bd_cmp(lfs2_t *lfs2,
uint8_t dat[8];
diff = lfs2_min(size-i, sizeof(dat));
- int res = lfs2_bd_read(lfs2,
+ int err = lfs2_bd_read(lfs2,
pcache, rcache, hint-i,
block, off+i, &dat, diff);
- if (res) {
- return res;
+ if (err) {
+ return err;
}
- res = memcmp(dat, data + i, diff);
+ int res = memcmp(dat, data + i, diff);
if (res) {
return res < 0 ? LFS2_CMP_LT : LFS2_CMP_GT;
}
@@ -151,11 +151,32 @@ static int lfs2_bd_cmp(lfs2_t *lfs2,
return LFS2_CMP_EQ;
}
+static int lfs2_bd_crc(lfs2_t *lfs2,
+ const lfs2_cache_t *pcache, lfs2_cache_t *rcache, lfs2_size_t hint,
+ lfs2_block_t block, lfs2_off_t off, lfs2_size_t size, uint32_t *crc) {
+ lfs2_size_t diff = 0;
+
+ for (lfs2_off_t i = 0; i < size; i += diff) {
+ uint8_t dat[8];
+ diff = lfs2_min(size-i, sizeof(dat));
+ int err = lfs2_bd_read(lfs2,
+ pcache, rcache, hint-i,
+ block, off+i, &dat, diff);
+ if (err) {
+ return err;
+ }
+
+ *crc = lfs2_crc(*crc, &dat, diff);
+ }
+
+ return 0;
+}
+
#ifndef LFS2_READONLY
static int lfs2_bd_flush(lfs2_t *lfs2,
lfs2_cache_t *pcache, lfs2_cache_t *rcache, bool validate) {
if (pcache->block != LFS2_BLOCK_NULL && pcache->block != LFS2_BLOCK_INLINE) {
- LFS2_ASSERT(pcache->block < lfs2->cfg->block_count);
+ LFS2_ASSERT(pcache->block < lfs2->block_count);
lfs2_size_t diff = lfs2_alignup(pcache->size, lfs2->cfg->prog_size);
int err = lfs2->cfg->prog(lfs2->cfg, pcache->block,
pcache->off, pcache->buffer, diff);
@@ -208,7 +229,7 @@ static int lfs2_bd_prog(lfs2_t *lfs2,
lfs2_block_t block, lfs2_off_t off,
const void *buffer, lfs2_size_t size) {
const uint8_t *data = buffer;
- LFS2_ASSERT(block == LFS2_BLOCK_INLINE || block < lfs2->cfg->block_count);
+ LFS2_ASSERT(block == LFS2_BLOCK_INLINE || block < lfs2->block_count);
LFS2_ASSERT(off + size <= lfs2->cfg->block_size);
while (size > 0) {
@@ -252,7 +273,7 @@ static int lfs2_bd_prog(lfs2_t *lfs2,
#ifndef LFS2_READONLY
static int lfs2_bd_erase(lfs2_t *lfs2, lfs2_block_t block) {
- LFS2_ASSERT(block < lfs2->cfg->block_count);
+ LFS2_ASSERT(block < lfs2->block_count);
int err = lfs2->cfg->erase(lfs2->cfg, block);
LFS2_ASSERT(err <= 0);
return err;
@@ -279,14 +300,12 @@ static inline int lfs2_pair_cmp(
paira[0] == pairb[1] || paira[1] == pairb[0]);
}
-#ifndef LFS2_READONLY
-static inline bool lfs2_pair_sync(
+static inline bool lfs2_pair_issync(
const lfs2_block_t paira[2],
const lfs2_block_t pairb[2]) {
return (paira[0] == pairb[0] && paira[1] == pairb[1]) ||
(paira[0] == pairb[1] && paira[1] == pairb[0]);
}
-#endif
static inline void lfs2_pair_fromle32(lfs2_block_t pair[2]) {
pair[0] = lfs2_fromle32(pair[0]);
@@ -325,6 +344,10 @@ static inline uint16_t lfs2_tag_type1(lfs2_tag_t tag) {
return (tag & 0x70000000) >> 20;
}
+static inline uint16_t lfs2_tag_type2(lfs2_tag_t tag) {
+ return (tag & 0x78000000) >> 20;
+}
+
static inline uint16_t lfs2_tag_type3(lfs2_tag_t tag) {
return (tag & 0x7ff00000) >> 20;
}
@@ -386,7 +409,7 @@ static inline bool lfs2_gstate_hasorphans(const lfs2_gstate_t *a) {
}
static inline uint8_t lfs2_gstate_getorphans(const lfs2_gstate_t *a) {
- return lfs2_tag_size(a->tag);
+ return lfs2_tag_size(a->tag) & 0x1ff;
}
static inline bool lfs2_gstate_hasmove(const lfs2_gstate_t *a) {
@@ -394,6 +417,10 @@ static inline bool lfs2_gstate_hasmove(const lfs2_gstate_t *a) {
}
#endif
+static inline bool lfs2_gstate_needssuperblock(const lfs2_gstate_t *a) {
+ return lfs2_tag_size(a->tag) >> 9;
+}
+
static inline bool lfs2_gstate_hasmovehere(const lfs2_gstate_t *a,
const lfs2_block_t *pair) {
return lfs2_tag_type1(a->tag) && lfs2_pair_cmp(a->pair, pair) == 0;
@@ -413,6 +440,24 @@ static inline void lfs2_gstate_tole32(lfs2_gstate_t *a) {
}
#endif
+// operations on forward-CRCs used to track erased state
+struct lfs2_fcrc {
+ lfs2_size_t size;
+ uint32_t crc;
+};
+
+static void lfs2_fcrc_fromle32(struct lfs2_fcrc *fcrc) {
+ fcrc->size = lfs2_fromle32(fcrc->size);
+ fcrc->crc = lfs2_fromle32(fcrc->crc);
+}
+
+#ifndef LFS2_READONLY
+static void lfs2_fcrc_tole32(struct lfs2_fcrc *fcrc) {
+ fcrc->size = lfs2_tole32(fcrc->size);
+ fcrc->crc = lfs2_tole32(fcrc->crc);
+}
+#endif
+
// other endianness operations
static void lfs2_ctz_fromle32(struct lfs2_ctz *ctz) {
ctz->head = lfs2_fromle32(ctz->head);
@@ -473,6 +518,28 @@ static void lfs2_mlist_append(lfs2_t *lfs2, struct lfs2_mlist *mlist) {
lfs2->mlist = mlist;
}
+// some other filesystem operations
+static uint32_t lfs2_fs_disk_version(lfs2_t *lfs2) {
+ (void)lfs2;
+#ifdef LFS2_MULTIVERSION
+ if (lfs2->cfg->disk_version) {
+ return lfs2->cfg->disk_version;
+ } else
+#endif
+ {
+ return LFS2_DISK_VERSION;
+ }
+}
+
+static uint16_t lfs2_fs_disk_version_major(lfs2_t *lfs2) {
+ return 0xffff & (lfs2_fs_disk_version(lfs2) >> 16);
+
+}
+
+static uint16_t lfs2_fs_disk_version_minor(lfs2_t *lfs2) {
+ return 0xffff & (lfs2_fs_disk_version(lfs2) >> 0);
+}
+
/// Internal operations predeclared here ///
#ifndef LFS2_READONLY
@@ -500,6 +567,8 @@ static lfs2_stag_t lfs2_fs_parent(lfs2_t *lfs2, const lfs2_block_t dir[2],
static int lfs2_fs_forceconsistency(lfs2_t *lfs2);
#endif
+static void lfs2_fs_prepsuperblock(lfs2_t *lfs2, bool needssuperblock);
+
#ifdef LFS2_MIGRATE
static int lfs21_traverse(lfs2_t *lfs2,
int (*cb)(void*, lfs2_block_t), void *data);
@@ -528,7 +597,7 @@ static int lfs2_rawunmount(lfs2_t *lfs2);
static int lfs2_alloc_lookahead(void *p, lfs2_block_t block) {
lfs2_t *lfs2 = (lfs2_t*)p;
lfs2_block_t off = ((block - lfs2->free.off)
- + lfs2->cfg->block_count) % lfs2->cfg->block_count;
+ + lfs2->block_count) % lfs2->block_count;
if (off < lfs2->free.size) {
lfs2->free.buffer[off / 32] |= 1U << (off % 32);
@@ -542,7 +611,7 @@ static int lfs2_alloc_lookahead(void *p, lfs2_block_t block) {
// is to prevent blocks from being garbage collected in the middle of a
// commit operation
static void lfs2_alloc_ack(lfs2_t *lfs2) {
- lfs2->free.ack = lfs2->cfg->block_count;
+ lfs2->free.ack = lfs2->block_count;
}
// drop the lookahead buffer, this is done during mounting and failed
@@ -554,6 +623,26 @@ static void lfs2_alloc_drop(lfs2_t *lfs2) {
}
#ifndef LFS2_READONLY
+static int lfs2_fs_rawgc(lfs2_t *lfs2) {
+ // Move free offset at the first unused block (lfs2->free.i)
+ // lfs2->free.i is equal lfs2->free.size when all blocks are used
+ lfs2->free.off = (lfs2->free.off + lfs2->free.i) % lfs2->block_count;
+ lfs2->free.size = lfs2_min(8*lfs2->cfg->lookahead_size, lfs2->free.ack);
+ lfs2->free.i = 0;
+
+ // find mask of free blocks from tree
+ memset(lfs2->free.buffer, 0, lfs2->cfg->lookahead_size);
+ int err = lfs2_fs_rawtraverse(lfs2, lfs2_alloc_lookahead, lfs2, true);
+ if (err) {
+ lfs2_alloc_drop(lfs2);
+ return err;
+ }
+
+ return 0;
+}
+#endif
+
+#ifndef LFS2_READONLY
static int lfs2_alloc(lfs2_t *lfs2, lfs2_block_t *block) {
while (true) {
while (lfs2->free.i != lfs2->free.size) {
@@ -563,7 +652,7 @@ static int lfs2_alloc(lfs2_t *lfs2, lfs2_block_t *block) {
if (!(lfs2->free.buffer[off / 32] & (1U << (off % 32)))) {
// found a free block
- *block = (lfs2->free.off + off) % lfs2->cfg->block_count;
+ *block = (lfs2->free.off + off) % lfs2->block_count;
// eagerly find next off so an alloc ack can
// discredit old lookahead blocks
@@ -585,16 +674,8 @@ static int lfs2_alloc(lfs2_t *lfs2, lfs2_block_t *block) {
return LFS2_ERR_NOSPC;
}
- lfs2->free.off = (lfs2->free.off + lfs2->free.size)
- % lfs2->cfg->block_count;
- lfs2->free.size = lfs2_min(8*lfs2->cfg->lookahead_size, lfs2->free.ack);
- lfs2->free.i = 0;
-
- // find mask of free blocks from tree
- memset(lfs2->free.buffer, 0, lfs2->cfg->lookahead_size);
- int err = lfs2_fs_rawtraverse(lfs2, lfs2_alloc_lookahead, lfs2, true);
- if (err) {
- lfs2_alloc_drop(lfs2);
+ int err = lfs2_fs_rawgc(lfs2);
+ if(err) {
return err;
}
}
@@ -808,7 +889,7 @@ static int lfs2_dir_traverse(lfs2_t *lfs2,
// iterate over directory and attrs
lfs2_tag_t tag;
const void *buffer;
- struct lfs2_diskoff disk;
+ struct lfs2_diskoff disk = {0};
while (true) {
{
if (off+lfs2_tag_dsize(ptag) < dir->off) {
@@ -998,7 +1079,8 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2,
// if either block address is invalid we return LFS2_ERR_CORRUPT here,
// otherwise later writes to the pair could fail
- if (pair[0] >= lfs2->cfg->block_count || pair[1] >= lfs2->cfg->block_count) {
+ if (lfs2->block_count
+ && (pair[0] >= lfs2->block_count || pair[1] >= lfs2->block_count)) {
return LFS2_ERR_CORRUPT;
}
@@ -1035,6 +1117,11 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2,
bool tempsplit = false;
lfs2_stag_t tempbesttag = besttag;
+ // assume not erased until proven otherwise
+ bool maybeerased = false;
+ bool hasfcrc = false;
+ struct lfs2_fcrc fcrc;
+
dir->rev = lfs2_tole32(dir->rev);
uint32_t crc = lfs2_crc(0xffffffff, &dir->rev, sizeof(dir->rev));
dir->rev = lfs2_fromle32(dir->rev);
@@ -1049,7 +1136,6 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2,
if (err) {
if (err == LFS2_ERR_CORRUPT) {
// can't continue?
- dir->erased = false;
break;
}
return err;
@@ -1058,19 +1144,19 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2,
crc = lfs2_crc(crc, &tag, sizeof(tag));
tag = lfs2_frombe32(tag) ^ ptag;
- // next commit not yet programmed or we're not in valid range
+ // next commit not yet programmed?
if (!lfs2_tag_isvalid(tag)) {
- dir->erased = (lfs2_tag_type1(ptag) == LFS2_TYPE_CRC &&
- dir->off % lfs2->cfg->prog_size == 0);
+ // we only might be erased if the last tag was a crc
+ maybeerased = (lfs2_tag_type2(ptag) == LFS2_TYPE_CCRC);
break;
+ // out of range?
} else if (off + lfs2_tag_dsize(tag) > lfs2->cfg->block_size) {
- dir->erased = false;
break;
}
ptag = tag;
- if (lfs2_tag_type1(tag) == LFS2_TYPE_CRC) {
+ if (lfs2_tag_type2(tag) == LFS2_TYPE_CCRC) {
// check the crc attr
uint32_t dcrc;
err = lfs2_bd_read(lfs2,
@@ -1078,7 +1164,6 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2,
dir->pair[0], off+sizeof(tag), &dcrc, sizeof(dcrc));
if (err) {
if (err == LFS2_ERR_CORRUPT) {
- dir->erased = false;
break;
}
return err;
@@ -1086,7 +1171,6 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2,
dcrc = lfs2_fromle32(dcrc);
if (crc != dcrc) {
- dir->erased = false;
break;
}
@@ -1108,26 +1192,21 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2,
dir->tail[1] = temptail[1];
dir->split = tempsplit;
- // reset crc
+ // reset crc, hasfcrc
crc = 0xffffffff;
continue;
}
// crc the entry first, hopefully leaving it in the cache
- for (lfs2_off_t j = sizeof(tag); j < lfs2_tag_dsize(tag); j++) {
- uint8_t dat;
- err = lfs2_bd_read(lfs2,
- NULL, &lfs2->rcache, lfs2->cfg->block_size,
- dir->pair[0], off+j, &dat, 1);
- if (err) {
- if (err == LFS2_ERR_CORRUPT) {
- dir->erased = false;
- break;
- }
- return err;
+ err = lfs2_bd_crc(lfs2,
+ NULL, &lfs2->rcache, lfs2->cfg->block_size,
+ dir->pair[0], off+sizeof(tag),
+ lfs2_tag_dsize(tag)-sizeof(tag), &crc);
+ if (err) {
+ if (err == LFS2_ERR_CORRUPT) {
+ break;
}
-
- crc = lfs2_crc(crc, &dat, 1);
+ return err;
}
// directory modification tags?
@@ -1154,11 +1233,24 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2,
dir->pair[0], off+sizeof(tag), &temptail, 8);
if (err) {
if (err == LFS2_ERR_CORRUPT) {
- dir->erased = false;
break;
}
+ return err;
}
lfs2_pair_fromle32(temptail);
+ } else if (lfs2_tag_type3(tag) == LFS2_TYPE_FCRC) {
+ err = lfs2_bd_read(lfs2,
+ NULL, &lfs2->rcache, lfs2->cfg->block_size,
+ dir->pair[0], off+sizeof(tag),
+ &fcrc, sizeof(fcrc));
+ if (err) {
+ if (err == LFS2_ERR_CORRUPT) {
+ break;
+ }
+ }
+
+ lfs2_fcrc_fromle32(&fcrc);
+ hasfcrc = true;
}
// found a match for our fetcher?
@@ -1167,7 +1259,6 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2,
dir->pair[0], off+sizeof(tag)});
if (res < 0) {
if (res == LFS2_ERR_CORRUPT) {
- dir->erased = false;
break;
}
return res;
@@ -1189,35 +1280,67 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2,
}
}
- // consider what we have good enough
- if (dir->off > 0) {
- // synthetic move
- if (lfs2_gstate_hasmovehere(&lfs2->gdisk, dir->pair)) {
- if (lfs2_tag_id(lfs2->gdisk.tag) == lfs2_tag_id(besttag)) {
- besttag |= 0x80000000;
- } else if (besttag != -1 &&
- lfs2_tag_id(lfs2->gdisk.tag) < lfs2_tag_id(besttag)) {
- besttag -= LFS2_MKTAG(0, 1, 0);
+ // found no valid commits?
+ if (dir->off == 0) {
+ // try the other block?
+ lfs2_pair_swap(dir->pair);
+ dir->rev = revs[(r+1)%2];
+ continue;
+ }
+
+ // did we end on a valid commit? we may have an erased block
+ dir->erased = false;
+ if (maybeerased && dir->off % lfs2->cfg->prog_size == 0) {
+ #ifdef LFS2_MULTIVERSION
+ // note versions < lfs22.1 did not have fcrc tags, if
+ // we're < lfs22.1 treat missing fcrc as erased data
+ //
+ // we don't strictly need to do this, but otherwise writing
+ // to lfs22.0 disks becomes very inefficient
+ if (lfs2_fs_disk_version(lfs2) < 0x00020001) {
+ dir->erased = true;
+
+ } else
+ #endif
+ if (hasfcrc) {
+ // check for an fcrc matching the next prog's erased state, if
+ // this failed most likely a previous prog was interrupted, we
+ // need a new erase
+ uint32_t fcrc_ = 0xffffffff;
+ int err = lfs2_bd_crc(lfs2,
+ NULL, &lfs2->rcache, lfs2->cfg->block_size,
+ dir->pair[0], dir->off, fcrc.size, &fcrc_);
+ if (err && err != LFS2_ERR_CORRUPT) {
+ return err;
}
- }
- // found tag? or found best id?
- if (id) {
- *id = lfs2_min(lfs2_tag_id(besttag), dir->count);
+ // found beginning of erased part?
+ dir->erased = (fcrc_ == fcrc.crc);
}
+ }
- if (lfs2_tag_isvalid(besttag)) {
- return besttag;
- } else if (lfs2_tag_id(besttag) < dir->count) {
- return LFS2_ERR_NOENT;
- } else {
- return 0;
+ // synthetic move
+ if (lfs2_gstate_hasmovehere(&lfs2->gdisk, dir->pair)) {
+ if (lfs2_tag_id(lfs2->gdisk.tag) == lfs2_tag_id(besttag)) {
+ besttag |= 0x80000000;
+ } else if (besttag != -1 &&
+ lfs2_tag_id(lfs2->gdisk.tag) < lfs2_tag_id(besttag)) {
+ besttag -= LFS2_MKTAG(0, 1, 0);
}
}
- // failed, try the other block?
- lfs2_pair_swap(dir->pair);
- dir->rev = revs[(r+1)%2];
+ // found tag? or found best id?
+ if (id) {
+ *id = lfs2_min(lfs2_tag_id(besttag), dir->count);
+ }
+
+ if (lfs2_tag_isvalid(besttag)) {
+ return besttag;
+ } else if (lfs2_tag_id(besttag) < dir->count) {
+ return LFS2_ERR_NOENT;
+ } else {
+ return 0;
+ }
}
LFS2_ERROR("Corrupted dir pair at {0x%"PRIx32", 0x%"PRIx32"}",
@@ -1491,9 +1614,15 @@ static int lfs2_dir_commitattr(lfs2_t *lfs2, struct lfs2_commit *commit,
#endif
#ifndef LFS2_READONLY
+
static int lfs2_dir_commitcrc(lfs2_t *lfs2, struct lfs2_commit *commit) {
// align to program units
- const lfs2_off_t end = lfs2_alignup(commit->off + 2*sizeof(uint32_t),
+ //
+ // this gets a bit complex as we have two types of crcs:
+ // - 5-word crc with fcrc to check following prog (middle of block)
+ // - 2-word crc with no following prog (end of block)
+ const lfs2_off_t end = lfs2_alignup(
+ lfs2_min(commit->off + 5*sizeof(uint32_t), lfs2->cfg->block_size),
lfs2->cfg->prog_size);
lfs2_off_t off1 = 0;
@@ -1503,89 +1632,128 @@ static int lfs2_dir_commitcrc(lfs2_t *lfs2, struct lfs2_commit *commit) {
// padding is not crced, which lets fetches skip padding but
// makes committing a bit more complicated
while (commit->off < end) {
- lfs2_off_t off = commit->off + sizeof(lfs2_tag_t);
- lfs2_off_t noff = lfs2_min(end - off, 0x3fe) + off;
+ lfs2_off_t noff = (
+ lfs2_min(end - (commit->off+sizeof(lfs2_tag_t)), 0x3fe)
+ + (commit->off+sizeof(lfs2_tag_t)));
+ // too large for crc tag? need padding commits
if (noff < end) {
- noff = lfs2_min(noff, end - 2*sizeof(uint32_t));
+ noff = lfs2_min(noff, end - 5*sizeof(uint32_t));
}
- // read erased state from next program unit
- lfs2_tag_t tag = 0xffffffff;
- int err = lfs2_bd_read(lfs2,
- NULL, &lfs2->rcache, sizeof(tag),
- commit->block, noff, &tag, sizeof(tag));
- if (err && err != LFS2_ERR_CORRUPT) {
- return err;
- }
+ // space for fcrc?
+ uint8_t eperturb = (uint8_t)-1;
+ if (noff >= end && noff <= lfs2->cfg->block_size - lfs2->cfg->prog_size) {
+ // first read the leading byte, this always contains a bit
+ // we can perturb to avoid writes that don't change the fcrc
+ int err = lfs2_bd_read(lfs2,
+ NULL, &lfs2->rcache, lfs2->cfg->prog_size,
+ commit->block, noff, &eperturb, 1);
+ if (err && err != LFS2_ERR_CORRUPT) {
+ return err;
+ }
- // build crc tag
- bool reset = ~lfs2_frombe32(tag) >> 31;
- tag = LFS2_MKTAG(LFS2_TYPE_CRC + reset, 0x3ff, noff - off);
+ #ifdef LFS2_MULTIVERSION
+ // unfortunately fcrcs break mdir fetching < lfs22.1, so only write
+ // these if we're a >= lfs22.1 filesystem
+ if (lfs2_fs_disk_version(lfs2) <= 0x00020000) {
+ // don't write fcrc
+ } else
+ #endif
+ {
+ // find the expected fcrc, don't bother avoiding a reread
+ // of the eperturb, it should still be in our cache
+ struct lfs2_fcrc fcrc = {
+ .size = lfs2->cfg->prog_size,
+ .crc = 0xffffffff
+ };
+ err = lfs2_bd_crc(lfs2,
+ NULL, &lfs2->rcache, lfs2->cfg->prog_size,
+ commit->block, noff, fcrc.size, &fcrc.crc);
+ if (err && err != LFS2_ERR_CORRUPT) {
+ return err;
+ }
+
+ lfs2_fcrc_tole32(&fcrc);
+ err = lfs2_dir_commitattr(lfs2, commit,
+ LFS2_MKTAG(LFS2_TYPE_FCRC, 0x3ff, sizeof(struct lfs2_fcrc)),
+ &fcrc);
+ if (err) {
+ return err;
+ }
+ }
+ }
- // write out crc
- uint32_t footer[2];
- footer[0] = lfs2_tobe32(tag ^ commit->ptag);
- commit->crc = lfs2_crc(commit->crc, &footer[0], sizeof(footer[0]));
- footer[1] = lfs2_tole32(commit->crc);
- err = lfs2_bd_prog(lfs2,
+ // build commit crc
+ struct {
+ lfs2_tag_t tag;
+ uint32_t crc;
+ } ccrc;
+ lfs2_tag_t ntag = LFS2_MKTAG(
+ LFS2_TYPE_CCRC + (((uint8_t)~eperturb) >> 7), 0x3ff,
+ noff - (commit->off+sizeof(lfs2_tag_t)));
+ ccrc.tag = lfs2_tobe32(ntag ^ commit->ptag);
+ commit->crc = lfs2_crc(commit->crc, &ccrc.tag, sizeof(lfs2_tag_t));
+ ccrc.crc = lfs2_tole32(commit->crc);
+
+ int err = lfs2_bd_prog(lfs2,
&lfs2->pcache, &lfs2->rcache, false,
- commit->block, commit->off, &footer, sizeof(footer));
+ commit->block, commit->off, &ccrc, sizeof(ccrc));
if (err) {
return err;
}
// keep track of non-padding checksum to verify
if (off1 == 0) {
- off1 = commit->off + sizeof(uint32_t);
+ off1 = commit->off + sizeof(lfs2_tag_t);
crc1 = commit->crc;
}
- commit->off += sizeof(tag)+lfs2_tag_size(tag);
- commit->ptag = tag ^ ((lfs2_tag_t)reset << 31);
- commit->crc = 0xffffffff; // reset crc for next "commit"
- }
+ commit->off = noff;
+ // perturb valid bit?
+ commit->ptag = ntag ^ ((0x80UL & ~eperturb) << 24);
+ // reset crc for next commit
+ commit->crc = 0xffffffff;
- // flush buffers
- int err = lfs2_bd_sync(lfs2, &lfs2->pcache, &lfs2->rcache, false);
- if (err) {
- return err;
+ // manually flush here since we don't prog the padding, this confuses
+ // the caching layer
+ if (noff >= end || noff >= lfs2->pcache.off + lfs2->cfg->cache_size) {
+ // flush buffers
+ int err = lfs2_bd_sync(lfs2, &lfs2->pcache, &lfs2->rcache, false);
+ if (err) {
+ return err;
+ }
+ }
}
// successful commit, check checksums to make sure
+ //
+ // note that we don't need to check padding commits, worst
+ // case if they are corrupted we would have had to compact anyways
lfs2_off_t off = commit->begin;
- lfs2_off_t noff = off1;
- while (off < end) {
- uint32_t crc = 0xffffffff;
- for (lfs2_off_t i = off; i < noff+sizeof(uint32_t); i++) {
- // check against written crc, may catch blocks that
- // become readonly and match our commit size exactly
- if (i == off1 && crc != crc1) {
- return LFS2_ERR_CORRUPT;
- }
-
- // leave it up to caching to make this efficient
- uint8_t dat;
- err = lfs2_bd_read(lfs2,
- NULL, &lfs2->rcache, noff+sizeof(uint32_t)-i,
- commit->block, i, &dat, 1);
- if (err) {
- return err;
- }
+ uint32_t crc = 0xffffffff;
+ int err = lfs2_bd_crc(lfs2,
+ NULL, &lfs2->rcache, off1+sizeof(uint32_t),
+ commit->block, off, off1-off, &crc);
+ if (err) {
+ return err;
+ }
- crc = lfs2_crc(crc, &dat, 1);
- }
+ // check non-padding commits against known crc
+ if (crc != crc1) {
+ return LFS2_ERR_CORRUPT;
+ }
- // detected write error?
- if (crc != 0) {
- return LFS2_ERR_CORRUPT;
- }
+ // make sure to check crc in case we happen to pick
+ // up an unrelated crc (frozen block?)
+ err = lfs2_bd_crc(lfs2,
+ NULL, &lfs2->rcache, sizeof(uint32_t),
+ commit->block, off1, sizeof(uint32_t), &crc);
+ if (err) {
+ return err;
+ }
- // skip padding
- off = lfs2_min(end - noff, 0x3fe) + noff;
- if (off < end) {
- off = lfs2_min(off, end - 2*sizeof(uint32_t));
- }
- noff = off + sizeof(uint32_t);
+ if (crc != 0) {
+ return LFS2_ERR_CORRUPT;
}
return 0;
@@ -1926,11 +2094,20 @@ static int lfs2_dir_splittingcompact(lfs2_t *lfs2, lfs2_mdir_t *dir,
return err;
}
- // space is complicated, we need room for tail, crc, gstate,
- // cleanup delete, and we cap at half a block to give room
- // for metadata updates.
+ // space is complicated, we need room for:
+ //
+ // - tail: 4+2*4 = 12 bytes
+ // - gstate: 4+3*4 = 16 bytes
+ // - move delete: 4 = 4 bytes
+ // - crc: 4+4 = 8 bytes
+ // total = 40 bytes
+ //
+ // And we cap at half a block to avoid degenerate cases with
+ // nearly-full metadata blocks.
+ //
if (end - split < 0xff
- && size <= lfs2_min(lfs2->cfg->block_size - 36,
+ && size <= lfs2_min(
+ lfs2->cfg->block_size - 40,
lfs2_alignup(
(lfs2->cfg->metadata_max
? lfs2->cfg->metadata_max
@@ -1976,7 +2153,7 @@ static int lfs2_dir_splittingcompact(lfs2_t *lfs2, lfs2_mdir_t *dir,
// do we have extra space? littlefs can't reclaim this space
// by itself, so expand cautiously
- if ((lfs2_size_t)size < lfs2->cfg->block_count/2) {
+ if ((lfs2_size_t)size < lfs2->block_count/2) {
LFS2_DEBUG("Expanding superblock at rev %"PRIu32, dir->rev);
int err = lfs2_dir_split(lfs2, dir, attrs, attrcount,
source, begin, end);
@@ -2594,11 +2771,6 @@ static int lfs2_dir_rawseek(lfs2_t *lfs2, lfs2_dir_t *dir, lfs2_off_t off) {
dir->id = (off > 0 && lfs2_pair_cmp(dir->head, lfs2->root) == 0);
while (off > 0) {
- int diff = lfs2_min(dir->m.count - dir->id, off);
- dir->id += diff;
- dir->pos += diff;
- off -= diff;
-
if (dir->id == dir->m.count) {
if (!dir->m.split) {
return LFS2_ERR_INVAL;
@@ -2611,6 +2783,11 @@ static int lfs2_dir_rawseek(lfs2_t *lfs2, lfs2_dir_t *dir, lfs2_off_t off) {
dir->id = 0;
}
+
+ int diff = lfs2_min(dir->m.count - dir->id, off);
+ dir->id += diff;
+ dir->pos += diff;
+ off -= diff;
}
return 0;
@@ -3347,7 +3524,7 @@ static lfs2_ssize_t lfs2_file_flushedwrite(lfs2_t *lfs2, lfs2_file_t *file,
// find out which block we're extending from
int err = lfs2_ctz_find(lfs2, NULL, &file->cache,
file->ctz.head, file->ctz.size,
- file->pos-1, &file->block, &file->off);
+ file->pos-1, &file->block, &(lfs2_off_t){0});
if (err) {
file->flags |= LFS2_F_ERRED;
return err;
@@ -3525,26 +3702,55 @@ static int lfs2_file_rawtruncate(lfs2_t *lfs2, lfs2_file_t *file, lfs2_off_t siz
lfs2_off_t pos = file->pos;
lfs2_off_t oldsize = lfs2_file_rawsize(lfs2, file);
if (size < oldsize) {
- // need to flush since directly changing metadata
- int err = lfs2_file_flush(lfs2, file);
- if (err) {
- return err;
- }
+ // revert to inline file?
+ if (size <= lfs2_min(0x3fe, lfs2_min(
+ lfs2->cfg->cache_size,
+ (lfs2->cfg->metadata_max ?
+ lfs2->cfg->metadata_max : lfs2->cfg->block_size) / 8))) {
+ // flush+seek to head
+ lfs2_soff_t res = lfs2_file_rawseek(lfs2, file, 0, LFS2_SEEK_SET);
+ if (res < 0) {
+ return (int)res;
+ }
- // lookup new head in ctz skip list
- err = lfs2_ctz_find(lfs2, NULL, &file->cache,
- file->ctz.head, file->ctz.size,
- size, &file->block, &file->off);
- if (err) {
- return err;
- }
+ // read our data into rcache temporarily
+ lfs2_cache_drop(lfs2, &lfs2->rcache);
+ res = lfs2_file_flushedread(lfs2, file,
+ lfs2->rcache.buffer, size);
+ if (res < 0) {
+ return (int)res;
+ }
- // need to set pos/block/off consistently so seeking back to
- // the old position does not get confused
- file->pos = size;
- file->ctz.head = file->block;
- file->ctz.size = size;
- file->flags |= LFS2_F_DIRTY | LFS2_F_READING;
+ file->ctz.head = LFS2_BLOCK_INLINE;
+ file->ctz.size = size;
+ file->flags |= LFS2_F_DIRTY | LFS2_F_READING | LFS2_F_INLINE;
+ file->cache.block = file->ctz.head;
+ file->cache.off = 0;
+ file->cache.size = lfs2->cfg->cache_size;
+ memcpy(file->cache.buffer, lfs2->rcache.buffer, size);
+
+ } else {
+ // need to flush since directly changing metadata
+ int err = lfs2_file_flush(lfs2, file);
+ if (err) {
+ return err;
+ }
+
+ // lookup new head in ctz skip list
+ err = lfs2_ctz_find(lfs2, NULL, &file->cache,
+ file->ctz.head, file->ctz.size,
+ size-1, &file->block, &(lfs2_off_t){0});
+ if (err) {
+ return err;
+ }
+
+ // need to set pos/block/off consistently so seeking back to
+ // the old position does not get confused
+ file->pos = size;
+ file->ctz.head = file->block;
+ file->ctz.size = size;
+ file->flags |= LFS2_F_DIRTY | LFS2_F_READING;
+ }
} else if (size > oldsize) {
// flush+seek if not already at end
lfs2_soff_t res = lfs2_file_rawseek(lfs2, file, 0, LFS2_SEEK_END);
@@ -3902,8 +4108,24 @@ static int lfs2_rawremoveattr(lfs2_t *lfs2, const char *path, uint8_t type) {
/// Filesystem operations ///
static int lfs2_init(lfs2_t *lfs2, const struct lfs2_config *cfg) {
lfs2->cfg = cfg;
+ lfs2->block_count = cfg->block_count; // May be 0
int err = 0;
+#ifdef LFS2_MULTIVERSION
+ // this driver only supports minor version < current minor version
+ LFS2_ASSERT(!lfs2->cfg->disk_version || (
+ (0xffff & (lfs2->cfg->disk_version >> 16))
+ == LFS2_DISK_VERSION_MAJOR
+ && (0xffff & (lfs2->cfg->disk_version >> 0))
+ <= LFS2_DISK_VERSION_MINOR));
+#endif
+
+ // check that bool is a truthy-preserving type
+ //
+ // note the most common reason for this failure is a before-c99 compiler,
+ // which littlefs currently does not support
+ LFS2_ASSERT((bool)0x80000000);
+
// validate that the lfs2-cfg sizes were initiated properly before
// performing any arithmetic logics with them
LFS2_ASSERT(lfs2->cfg->read_size != 0);
@@ -3916,7 +4138,10 @@ static int lfs2_init(lfs2_t *lfs2, const struct lfs2_config *cfg) {
LFS2_ASSERT(lfs2->cfg->cache_size % lfs2->cfg->prog_size == 0);
LFS2_ASSERT(lfs2->cfg->block_size % lfs2->cfg->cache_size == 0);
- // check that the block size is large enough to fit ctz pointers
+ // check that the block size is large enough to fit all ctz pointers
+ LFS2_ASSERT(lfs2->cfg->block_size >= 128);
+ // this is the exact calculation for all ctz pointers, if this fails
+ // and the simpler assert above does not, math must be broken
LFS2_ASSERT(4*lfs2_npw2(0xffffffff / (lfs2->cfg->block_size-2*4))
<= lfs2->cfg->block_size);
@@ -4026,6 +4251,8 @@ static int lfs2_deinit(lfs2_t *lfs2) {
return 0;
}
+
+
#ifndef LFS2_READONLY
static int lfs2_rawformat(lfs2_t *lfs2, const struct lfs2_config *cfg) {
int err = 0;
@@ -4035,11 +4262,13 @@ static int lfs2_rawformat(lfs2_t *lfs2, const struct lfs2_config *cfg) {
return err;
}
+ LFS2_ASSERT(cfg->block_count != 0);
+
// create free lookahead
memset(lfs2->free.buffer, 0, lfs2->cfg->lookahead_size);
lfs2->free.off = 0;
lfs2->free.size = lfs2_min(8*lfs2->cfg->lookahead_size,
- lfs2->cfg->block_count);
+ lfs2->block_count);
lfs2->free.i = 0;
lfs2_alloc_ack(lfs2);
@@ -4052,9 +4281,9 @@ static int lfs2_rawformat(lfs2_t *lfs2, const struct lfs2_config *cfg) {
// write one superblock
lfs2_superblock_t superblock = {
- .version = LFS2_DISK_VERSION,
+ .version = lfs2_fs_disk_version(lfs2),
.block_size = lfs2->cfg->block_size,
- .block_count = lfs2->cfg->block_count,
+ .block_count = lfs2->block_count,
.name_max = lfs2->name_max,
.file_max = lfs2->file_max,
.attr_max = lfs2->attr_max,
@@ -4100,14 +4329,23 @@ static int lfs2_rawmount(lfs2_t *lfs2, const struct lfs2_config *cfg) {
// scan directory blocks for superblock and any global updates
lfs2_mdir_t dir = {.tail = {0, 1}};
- lfs2_block_t cycle = 0;
+ lfs2_block_t tortoise[2] = {LFS2_BLOCK_NULL, LFS2_BLOCK_NULL};
+ lfs2_size_t tortoise_i = 1;
+ lfs2_size_t tortoise_period = 1;
while (!lfs2_pair_isnull(dir.tail)) {
- if (cycle >= lfs2->cfg->block_count/2) {
- // loop detected
+ // detect cycles with Brent's algorithm
+ if (lfs2_pair_issync(dir.tail, tortoise)) {
+ LFS2_WARN("Cycle detected in tail list");
err = LFS2_ERR_CORRUPT;
goto cleanup;
}
- cycle += 1;
+ if (tortoise_i == tortoise_period) {
+ tortoise[0] = dir.tail[0];
+ tortoise[1] = dir.tail[1];
+ tortoise_i = 0;
+ tortoise_period *= 2;
+ }
+ tortoise_i += 1;
// fetch next block in tail list
lfs2_stag_t tag = lfs2_dir_fetchmatch(lfs2, &dir, dir.tail,
@@ -4141,14 +4379,33 @@ static int lfs2_rawmount(lfs2_t *lfs2, const struct lfs2_config *cfg) {
// check version
uint16_t major_version = (0xffff & (superblock.version >> 16));
uint16_t minor_version = (0xffff & (superblock.version >> 0));
- if ((major_version != LFS2_DISK_VERSION_MAJOR ||
- minor_version > LFS2_DISK_VERSION_MINOR)) {
- LFS2_ERROR("Invalid version v%"PRIu16".%"PRIu16,
- major_version, minor_version);
+ if (major_version != lfs2_fs_disk_version_major(lfs2)
+ || minor_version > lfs2_fs_disk_version_minor(lfs2)) {
+ LFS2_ERROR("Invalid version "
+ "v%"PRIu16".%"PRIu16" != v%"PRIu16".%"PRIu16,
+ major_version,
+ minor_version,
+ lfs2_fs_disk_version_major(lfs2),
+ lfs2_fs_disk_version_minor(lfs2));
err = LFS2_ERR_INVAL;
goto cleanup;
}
+ // found older minor version? set an in-device only bit in the
+ // gstate so we know we need to rewrite the superblock before
+ // the first write
+ if (minor_version < lfs2_fs_disk_version_minor(lfs2)) {
+ LFS2_DEBUG("Found older minor version "
+ "v%"PRIu16".%"PRIu16" < v%"PRIu16".%"PRIu16,
+ major_version,
+ minor_version,
+ lfs2_fs_disk_version_major(lfs2),
+ lfs2_fs_disk_version_minor(lfs2));
+ // note this bit is reserved on disk, so fetching more gstate
+ // will not interfere here
+ lfs2_fs_prepsuperblock(lfs2, true);
+ }
+
// check superblock configuration
if (superblock.name_max) {
if (superblock.name_max > lfs2->name_max) {
@@ -4183,16 +4440,20 @@ static int lfs2_rawmount(lfs2_t *lfs2, const struct lfs2_config *cfg) {
lfs2->attr_max = superblock.attr_max;
}
- if (superblock.block_count != lfs2->cfg->block_count) {
+ // this is where we get the block_count from disk if block_count=0
+ if (lfs2->cfg->block_count
+ && superblock.block_count != lfs2->cfg->block_count) {
LFS2_ERROR("Invalid block count (%"PRIu32" != %"PRIu32")",
superblock.block_count, lfs2->cfg->block_count);
err = LFS2_ERR_INVAL;
goto cleanup;
}
+ lfs2->block_count = superblock.block_count;
+
if (superblock.block_size != lfs2->cfg->block_size) {
LFS2_ERROR("Invalid block size (%"PRIu32" != %"PRIu32")",
- superblock.block_count, lfs2->cfg->block_count);
+ superblock.block_size, lfs2->cfg->block_size);
err = LFS2_ERR_INVAL;
goto cleanup;
}
@@ -4205,12 +4466,6 @@ static int lfs2_rawmount(lfs2_t *lfs2, const struct lfs2_config *cfg) {
}
}
- // found superblock?
- if (lfs2_pair_isnull(lfs2->root)) {
- err = LFS2_ERR_INVAL;
- goto cleanup;
- }
-
// update littlefs with gstate
if (!lfs2_gstate_iszero(&lfs2->gstate)) {
LFS2_DEBUG("Found pending gstate 0x%08"PRIx32"%08"PRIx32"%08"PRIx32,
@@ -4223,7 +4478,7 @@ static int lfs2_rawmount(lfs2_t *lfs2, const struct lfs2_config *cfg) {
// setup free lookahead, to distribute allocations uniformly across
// boots, we start the allocator at a random location
- lfs2->free.off = lfs2->seed % lfs2->cfg->block_count;
+ lfs2->free.off = lfs2->seed % lfs2->block_count;
lfs2_alloc_drop(lfs2);
return 0;
@@ -4239,6 +4494,46 @@ static int lfs2_rawunmount(lfs2_t *lfs2) {
/// Filesystem filesystem operations ///
+static int lfs2_fs_rawstat(lfs2_t *lfs2, struct lfs2_fsinfo *fsinfo) {
+ // if the superblock is up-to-date, we must be on the most recent
+ // minor version of littlefs
+ if (!lfs2_gstate_needssuperblock(&lfs2->gstate)) {
+ fsinfo->disk_version = lfs2_fs_disk_version(lfs2);
+
+ // otherwise we need to read the minor version on disk
+ } else {
+ // fetch the superblock
+ lfs2_mdir_t dir;
+ int err = lfs2_dir_fetch(lfs2, &dir, lfs2->root);
+ if (err) {
+ return err;
+ }
+
+ lfs2_superblock_t superblock;
+ lfs2_stag_t tag = lfs2_dir_get(lfs2, &dir, LFS2_MKTAG(0x7ff, 0x3ff, 0),
+ LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
+ &superblock);
+ if (tag < 0) {
+ return tag;
+ }
+ lfs2_superblock_fromle32(&superblock);
+
+ // read the on-disk version
+ fsinfo->disk_version = superblock.version;
+ }
+
+ // filesystem geometry
+ fsinfo->block_size = lfs2->cfg->block_size;
+ fsinfo->block_count = lfs2->block_count;
+
+ // other on-disk configuration, we cache all of these for internal use
+ fsinfo->name_max = lfs2->name_max;
+ fsinfo->file_max = lfs2->file_max;
+ fsinfo->attr_max = lfs2->attr_max;
+
+ return 0;
+}
+
int lfs2_fs_rawtraverse(lfs2_t *lfs2,
int (*cb)(void *data, lfs2_block_t block), void *data,
bool includeorphans) {
@@ -4258,13 +4553,22 @@ int lfs2_fs_rawtraverse(lfs2_t *lfs2,
}
#endif
- lfs2_block_t cycle = 0;
+ lfs2_block_t tortoise[2] = {LFS2_BLOCK_NULL, LFS2_BLOCK_NULL};
+ lfs2_size_t tortoise_i = 1;
+ lfs2_size_t tortoise_period = 1;
while (!lfs2_pair_isnull(dir.tail)) {
- if (cycle >= lfs2->cfg->block_count/2) {
- // loop detected
+ // detect cycles with Brent's algorithm
+ if (lfs2_pair_issync(dir.tail, tortoise)) {
+ LFS2_WARN("Cycle detected in tail list");
return LFS2_ERR_CORRUPT;
}
- cycle += 1;
+ if (tortoise_i == tortoise_period) {
+ tortoise[0] = dir.tail[0];
+ tortoise[1] = dir.tail[1];
+ tortoise_i = 0;
+ tortoise_period *= 2;
+ }
+ tortoise_i += 1;
for (int i = 0; i < 2; i++) {
int err = cb(data, dir.tail[i]);
@@ -4343,13 +4647,22 @@ static int lfs2_fs_pred(lfs2_t *lfs2,
// iterate over all directory directory entries
pdir->tail[0] = 0;
pdir->tail[1] = 1;
- lfs2_block_t cycle = 0;
+ lfs2_block_t tortoise[2] = {LFS2_BLOCK_NULL, LFS2_BLOCK_NULL};
+ lfs2_size_t tortoise_i = 1;
+ lfs2_size_t tortoise_period = 1;
while (!lfs2_pair_isnull(pdir->tail)) {
- if (cycle >= lfs2->cfg->block_count/2) {
- // loop detected
+ // detect cycles with Brent's algorithm
+ if (lfs2_pair_issync(pdir->tail, tortoise)) {
+ LFS2_WARN("Cycle detected in tail list");
return LFS2_ERR_CORRUPT;
}
- cycle += 1;
+ if (tortoise_i == tortoise_period) {
+ tortoise[0] = pdir->tail[0];
+ tortoise[1] = pdir->tail[1];
+ tortoise_i = 0;
+ tortoise_period *= 2;
+ }
+ tortoise_i += 1;
if (lfs2_pair_cmp(pdir->tail, pair) == 0) {
return 0;
@@ -4399,13 +4712,22 @@ static lfs2_stag_t lfs2_fs_parent(lfs2_t *lfs2, const lfs2_block_t pair[2],
// use fetchmatch with callback to find pairs
parent->tail[0] = 0;
parent->tail[1] = 1;
- lfs2_block_t cycle = 0;
+ lfs2_block_t tortoise[2] = {LFS2_BLOCK_NULL, LFS2_BLOCK_NULL};
+ lfs2_size_t tortoise_i = 1;
+ lfs2_size_t tortoise_period = 1;
while (!lfs2_pair_isnull(parent->tail)) {
- if (cycle >= lfs2->cfg->block_count/2) {
- // loop detected
+ // detect cycles with Brent's algorithm
+ if (lfs2_pair_issync(parent->tail, tortoise)) {
+ LFS2_WARN("Cycle detected in tail list");
return LFS2_ERR_CORRUPT;
}
- cycle += 1;
+ if (tortoise_i == tortoise_period) {
+ tortoise[0] = parent->tail[0];
+ tortoise[1] = parent->tail[1];
+ tortoise_i = 0;
+ tortoise_period *= 2;
+ }
+ tortoise_i += 1;
lfs2_stag_t tag = lfs2_dir_fetchmatch(lfs2, parent, parent->tail,
LFS2_MKTAG(0x7ff, 0, 0x3ff),
@@ -4422,9 +4744,15 @@ static lfs2_stag_t lfs2_fs_parent(lfs2_t *lfs2, const lfs2_block_t pair[2],
}
#endif
+static void lfs2_fs_prepsuperblock(lfs2_t *lfs2, bool needssuperblock) {
+ lfs2->gstate.tag = (lfs2->gstate.tag & ~LFS2_MKTAG(0, 0, 0x200))
+ | (uint32_t)needssuperblock << 9;
+}
+
#ifndef LFS2_READONLY
static int lfs2_fs_preporphans(lfs2_t *lfs2, int8_t orphans) {
- LFS2_ASSERT(lfs2_tag_size(lfs2->gstate.tag) > 0 || orphans >= 0);
+ LFS2_ASSERT(lfs2_tag_size(lfs2->gstate.tag) > 0x000 || orphans >= 0);
+ LFS2_ASSERT(lfs2_tag_size(lfs2->gstate.tag) < 0x1ff || orphans <= 0);
lfs2->gstate.tag += orphans;
lfs2->gstate.tag = ((lfs2->gstate.tag & ~LFS2_MKTAG(0x800, 0, 0)) |
((uint32_t)lfs2_gstate_hasorphans(&lfs2->gstate) << 31));
@@ -4444,6 +4772,45 @@ static void lfs2_fs_prepmove(lfs2_t *lfs2,
#endif
#ifndef LFS2_READONLY
+static int lfs2_fs_desuperblock(lfs2_t *lfs2) {
+ if (!lfs2_gstate_needssuperblock(&lfs2->gstate)) {
+ return 0;
+ }
+
+ LFS2_DEBUG("Rewriting superblock {0x%"PRIx32", 0x%"PRIx32"}",
+ lfs2->root[0],
+ lfs2->root[1]);
+
+ lfs2_mdir_t root;
+ int err = lfs2_dir_fetch(lfs2, &root, lfs2->root);
+ if (err) {
+ return err;
+ }
+
+ // write a new superblock
+ lfs2_superblock_t superblock = {
+ .version = lfs2_fs_disk_version(lfs2),
+ .block_size = lfs2->cfg->block_size,
+ .block_count = lfs2->block_count,
+ .name_max = lfs2->name_max,
+ .file_max = lfs2->file_max,
+ .attr_max = lfs2->attr_max,
+ };
+
+ lfs2_superblock_tole32(&superblock);
+ err = lfs2_dir_commit(lfs2, &root, LFS2_MKATTRS(
+ {LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
+ &superblock}));
+ if (err) {
+ return err;
+ }
+
+ lfs2_fs_prepsuperblock(lfs2, false);
+ return 0;
+}
+#endif
+
+#ifndef LFS2_READONLY
static int lfs2_fs_demove(lfs2_t *lfs2) {
if (!lfs2_gstate_hasmove(&lfs2->gdisk)) {
return 0;
@@ -4455,6 +4822,10 @@ static int lfs2_fs_demove(lfs2_t *lfs2) {
lfs2->gdisk.pair[1],
lfs2_tag_id(lfs2->gdisk.tag));
+ // no other gstate is supported at this time, so if we found something else
+ // something most likely went wrong in gstate calculation
+ LFS2_ASSERT(lfs2_tag_type3(lfs2->gdisk.tag) == LFS2_TYPE_DELETE);
+
// fetch and delete the moved entry
lfs2_mdir_t movedir;
int err = lfs2_dir_fetch(lfs2, &movedir, lfs2->gdisk.pair);
@@ -4481,12 +4852,20 @@ static int lfs2_fs_deorphan(lfs2_t *lfs2, bool powerloss) {
return 0;
}
- int8_t found = 0;
-restart:
- {
+ // Check for orphans in two separate passes:
+ // - 1 for half-orphans (relocations)
+ // - 2 for full-orphans (removes/renames)
+ //
+ // Two separate passes are needed as half-orphans can contain outdated
+ // references to full-orphans, effectively hiding them from the deorphan
+ // search.
+ //
+ int pass = 0;
+ while (pass < 2) {
// Fix any orphans
lfs2_mdir_t pdir = {.split = true, .tail = {0, 1}};
lfs2_mdir_t dir;
+ bool moreorphans = false;
// iterate over all directory directory entries
while (!lfs2_pair_isnull(pdir.tail)) {
@@ -4504,42 +4883,7 @@ restart:
return tag;
}
- // note we only check for full orphans if we may have had a
- // power-loss, otherwise orphans are created intentionally
- // during operations such as lfs2_mkdir
- if (tag == LFS2_ERR_NOENT && powerloss) {
- // we are an orphan
- LFS2_DEBUG("Fixing orphan {0x%"PRIx32", 0x%"PRIx32"}",
- pdir.tail[0], pdir.tail[1]);
-
- // steal state
- err = lfs2_dir_getgstate(lfs2, &dir, &lfs2->gdelta);
- if (err) {
- return err;
- }
-
- // steal tail
- lfs2_pair_tole32(dir.tail);
- int state = lfs2_dir_orphaningcommit(lfs2, &pdir, LFS2_MKATTRS(
- {LFS2_MKTAG(LFS2_TYPE_TAIL + dir.split, 0x3ff, 8),
- dir.tail}));
- lfs2_pair_fromle32(dir.tail);
- if (state < 0) {
- return state;
- }
-
- found += 1;
-
- // did our commit create more orphans?
- if (state == LFS2_OK_ORPHANED) {
- goto restart;
- }
-
- // refetch tail
- continue;
- }
-
- if (tag != LFS2_ERR_NOENT) {
+ if (pass == 0 && tag != LFS2_ERR_NOENT) {
lfs2_block_t pair[2];
lfs2_stag_t state = lfs2_dir_get(lfs2, &parent,
LFS2_MKTAG(0x7ff, 0x3ff, 0), tag, pair);
@@ -4548,7 +4892,7 @@ restart:
}
lfs2_pair_fromle32(pair);
- if (!lfs2_pair_sync(pair, pdir.tail)) {
+ if (!lfs2_pair_issync(pair, pdir.tail)) {
// we have desynced
LFS2_DEBUG("Fixing half-orphan "
"{0x%"PRIx32", 0x%"PRIx32"} "
@@ -4578,33 +4922,69 @@ restart:
return state;
}
- found += 1;
-
// did our commit create more orphans?
if (state == LFS2_OK_ORPHANED) {
- goto restart;
+ moreorphans = true;
}
// refetch tail
continue;
}
}
+
+ // note we only check for full orphans if we may have had a
+ // power-loss, otherwise orphans are created intentionally
+ // during operations such as lfs2_mkdir
+ if (pass == 1 && tag == LFS2_ERR_NOENT && powerloss) {
+ // we are an orphan
+ LFS2_DEBUG("Fixing orphan {0x%"PRIx32", 0x%"PRIx32"}",
+ pdir.tail[0], pdir.tail[1]);
+
+ // steal state
+ err = lfs2_dir_getgstate(lfs2, &dir, &lfs2->gdelta);
+ if (err) {
+ return err;
+ }
+
+ // steal tail
+ lfs2_pair_tole32(dir.tail);
+ int state = lfs2_dir_orphaningcommit(lfs2, &pdir, LFS2_MKATTRS(
+ {LFS2_MKTAG(LFS2_TYPE_TAIL + dir.split, 0x3ff, 8),
+ dir.tail}));
+ lfs2_pair_fromle32(dir.tail);
+ if (state < 0) {
+ return state;
+ }
+
+ // did our commit create more orphans?
+ if (state == LFS2_OK_ORPHANED) {
+ moreorphans = true;
+ }
+
+ // refetch tail
+ continue;
+ }
}
pdir = dir;
}
+
+ pass = moreorphans ? 0 : pass+1;
}
// mark orphans as fixed
- return lfs2_fs_preporphans(lfs2, -lfs2_min(
- lfs2_gstate_getorphans(&lfs2->gstate),
- found));
+ return lfs2_fs_preporphans(lfs2, -lfs2_gstate_getorphans(&lfs2->gstate));
}
#endif
#ifndef LFS2_READONLY
static int lfs2_fs_forceconsistency(lfs2_t *lfs2) {
- int err = lfs2_fs_demove(lfs2);
+ int err = lfs2_fs_desuperblock(lfs2);
+ if (err) {
+ return err;
+ }
+
+ err = lfs2_fs_demove(lfs2);
if (err) {
return err;
}
@@ -4618,6 +4998,36 @@ static int lfs2_fs_forceconsistency(lfs2_t *lfs2) {
}
#endif
+#ifndef LFS2_READONLY
+static int lfs2_fs_rawmkconsistent(lfs2_t *lfs2) {
+ // lfs2_fs_forceconsistency does most of the work here
+ int err = lfs2_fs_forceconsistency(lfs2);
+ if (err) {
+ return err;
+ }
+
+ // do we have any pending gstate?
+ lfs2_gstate_t delta = {0};
+ lfs2_gstate_xor(&delta, &lfs2->gdisk);
+ lfs2_gstate_xor(&delta, &lfs2->gstate);
+ if (!lfs2_gstate_iszero(&delta)) {
+ // lfs2_dir_commit will implicitly write out any pending gstate
+ lfs2_mdir_t root;
+ err = lfs2_dir_fetch(lfs2, &root, lfs2->root);
+ if (err) {
+ return err;
+ }
+
+ err = lfs2_dir_commit(lfs2, &root, NULL, 0);
+ if (err) {
+ return err;
+ }
+ }
+
+ return 0;
+}
+#endif
+
static int lfs2_fs_size_count(void *p, lfs2_block_t block) {
(void)block;
lfs2_size_t *size = p;
@@ -4635,6 +5045,45 @@ static lfs2_ssize_t lfs2_fs_rawsize(lfs2_t *lfs2) {
return size;
}
+#ifndef LFS2_READONLY
+static int lfs2_fs_rawgrow(lfs2_t *lfs2, lfs2_size_t block_count) {
+ // shrinking is not supported
+ LFS2_ASSERT(block_count >= lfs2->block_count);
+
+ if (block_count > lfs2->block_count) {
+ lfs2->block_count = block_count;
+
+ // fetch the root
+ lfs2_mdir_t root;
+ int err = lfs2_dir_fetch(lfs2, &root, lfs2->root);
+ if (err) {
+ return err;
+ }
+
+ // update the superblock
+ lfs2_superblock_t superblock;
+ lfs2_stag_t tag = lfs2_dir_get(lfs2, &root, LFS2_MKTAG(0x7ff, 0x3ff, 0),
+ LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
+ &superblock);
+ if (tag < 0) {
+ return tag;
+ }
+ lfs2_superblock_fromle32(&superblock);
+
+ superblock.block_count = lfs2->block_count;
+
+ lfs2_superblock_tole32(&superblock);
+ err = lfs2_dir_commit(lfs2, &root, LFS2_MKATTRS(
+ {tag, &superblock}));
+ if (err) {
+ return err;
+ }
+ }
+
+ return 0;
+}
+#endif
+
#ifdef LFS2_MIGRATE
////// Migration from littelfs v1 below this //////
@@ -5058,6 +5507,10 @@ static int lfs21_unmount(lfs2_t *lfs2) {
/// v1 migration ///
static int lfs2_rawmigrate(lfs2_t *lfs2, const struct lfs2_config *cfg) {
struct lfs21 lfs21;
+
+ // Indeterminate filesystem size not allowed for migration.
+ LFS2_ASSERT(cfg->block_count != 0);
+
int err = lfs21_mount(lfs2, &lfs21, cfg);
if (err) {
return err;
@@ -5754,6 +6207,20 @@ int lfs2_dir_rewind(lfs2_t *lfs2, lfs2_dir_t *dir) {
return err;
}
+int lfs2_fs_stat(lfs2_t *lfs2, struct lfs2_fsinfo *fsinfo) {
+ int err = LFS2_LOCK(lfs2->cfg);
+ if (err) {
+ return err;
+ }
+ LFS2_TRACE("lfs2_fs_stat(%p, %p)", (void*)lfs2, (void*)fsinfo);
+
+ err = lfs2_fs_rawstat(lfs2, fsinfo);
+
+ LFS2_TRACE("lfs2_fs_stat -> %d", err);
+ LFS2_UNLOCK(lfs2->cfg);
+ return err;
+}
+
lfs2_ssize_t lfs2_fs_size(lfs2_t *lfs2) {
int err = LFS2_LOCK(lfs2->cfg);
if (err) {
@@ -5783,6 +6250,54 @@ int lfs2_fs_traverse(lfs2_t *lfs2, int (*cb)(void *, lfs2_block_t), void *data)
return err;
}
+#ifndef LFS2_READONLY
+int lfs2_fs_gc(lfs2_t *lfs2) {
+ int err = LFS2_LOCK(lfs2->cfg);
+ if (err) {
+ return err;
+ }
+ LFS2_TRACE("lfs2_fs_gc(%p)", (void*)lfs2);
+
+ err = lfs2_fs_rawgc(lfs2);
+
+ LFS2_TRACE("lfs2_fs_gc -> %d", err);
+ LFS2_UNLOCK(lfs2->cfg);
+ return err;
+}
+#endif
+
+#ifndef LFS2_READONLY
+int lfs2_fs_mkconsistent(lfs2_t *lfs2) {
+ int err = LFS2_LOCK(lfs2->cfg);
+ if (err) {
+ return err;
+ }
+ LFS2_TRACE("lfs2_fs_mkconsistent(%p)", (void*)lfs2);
+
+ err = lfs2_fs_rawmkconsistent(lfs2);
+
+ LFS2_TRACE("lfs2_fs_mkconsistent -> %d", err);
+ LFS2_UNLOCK(lfs2->cfg);
+ return err;
+}
+#endif
+
+#ifndef LFS2_READONLY
+int lfs2_fs_grow(lfs2_t *lfs2, lfs2_size_t block_count) {
+ int err = LFS2_LOCK(lfs2->cfg);
+ if (err) {
+ return err;
+ }
+ LFS2_TRACE("lfs2_fs_grow(%p, %"PRIu32")", (void*)lfs2, block_count);
+
+ err = lfs2_fs_rawgrow(lfs2, block_count);
+
+ LFS2_TRACE("lfs2_fs_grow -> %d", err);
+ LFS2_UNLOCK(lfs2->cfg);
+ return err;
+}
+#endif
+
#ifdef LFS2_MIGRATE
int lfs2_migrate(lfs2_t *lfs2, const struct lfs2_config *cfg) {
int err = LFS2_LOCK(cfg);
diff --git a/lib/littlefs/lfs2.h b/lib/littlefs/lfs2.h
index 715764f7c..4c426fc4c 100644
--- a/lib/littlefs/lfs2.h
+++ b/lib/littlefs/lfs2.h
@@ -8,8 +8,6 @@
#ifndef LFS2_H
#define LFS2_H
-#include <stdint.h>
-#include <stdbool.h>
#include "lfs2_util.h"
#ifdef __cplusplus
@@ -23,14 +21,14 @@ extern "C"
// Software library version
// Major (top-nibble), incremented on backwards incompatible changes
// Minor (bottom-nibble), incremented on feature additions
-#define LFS2_VERSION 0x00020005
+#define LFS2_VERSION 0x00020008
#define LFS2_VERSION_MAJOR (0xffff & (LFS2_VERSION >> 16))
#define LFS2_VERSION_MINOR (0xffff & (LFS2_VERSION >> 0))
// Version of On-disk data structures
// Major (top-nibble), incremented on backwards incompatible changes
// Minor (bottom-nibble), incremented on feature additions
-#define LFS2_DISK_VERSION 0x00020000
+#define LFS2_DISK_VERSION 0x00020001
#define LFS2_DISK_VERSION_MAJOR (0xffff & (LFS2_DISK_VERSION >> 16))
#define LFS2_DISK_VERSION_MINOR (0xffff & (LFS2_DISK_VERSION >> 0))
@@ -114,6 +112,8 @@ enum lfs2_type {
LFS2_TYPE_SOFTTAIL = 0x600,
LFS2_TYPE_HARDTAIL = 0x601,
LFS2_TYPE_MOVESTATE = 0x7ff,
+ LFS2_TYPE_CCRC = 0x500,
+ LFS2_TYPE_FCRC = 0x5ff,
// internal chip sources
LFS2_FROM_NOOP = 0x000,
@@ -263,6 +263,14 @@ struct lfs2_config {
// can help bound the metadata compaction time. Must be <= block_size.
// Defaults to block_size when zero.
lfs2_size_t metadata_max;
+
+#ifdef LFS2_MULTIVERSION
+ // On-disk version to use when writing in the form of 16-bit major version
+ // + 16-bit minor version. This limiting metadata to what is supported by
+ // older minor versions. Note that some features will be lost. Defaults to
+ // to the most recent minor version when zero.
+ uint32_t disk_version;
+#endif
};
// File info structure
@@ -280,6 +288,27 @@ struct lfs2_info {
char name[LFS2_NAME_MAX+1];
};
+// Filesystem info structure
+struct lfs2_fsinfo {
+ // On-disk version.
+ uint32_t disk_version;
+
+ // Size of a logical block in bytes.
+ lfs2_size_t block_size;
+
+ // Number of logical blocks in filesystem.
+ lfs2_size_t block_count;
+
+ // Upper limit on the length of file names in bytes.
+ lfs2_size_t name_max;
+
+ // Upper limit on the size of files in bytes.
+ lfs2_size_t file_max;
+
+ // Upper limit on the size of custom attributes in bytes.
+ lfs2_size_t attr_max;
+};
+
// Custom attribute structure, used to describe custom attributes
// committed atomically during file writes.
struct lfs2_attr {
@@ -410,6 +439,7 @@ typedef struct lfs2 {
} free;
const struct lfs2_config *cfg;
+ lfs2_size_t block_count;
lfs2_size_t name_max;
lfs2_size_t file_max;
lfs2_size_t attr_max;
@@ -534,8 +564,8 @@ int lfs2_file_open(lfs2_t *lfs2, lfs2_file_t *file,
// are values from the enum lfs2_open_flags that are bitwise-ored together.
//
// The config struct provides additional config options per file as described
-// above. The config struct must be allocated while the file is open, and the
-// config struct must be zeroed for defaults and backwards compatibility.
+// above. The config struct must remain allocated while the file is open, and
+// the config struct must be zeroed for defaults and backwards compatibility.
//
// Returns a negative error code on failure.
int lfs2_file_opencfg(lfs2_t *lfs2, lfs2_file_t *file,
@@ -659,6 +689,12 @@ int lfs2_dir_rewind(lfs2_t *lfs2, lfs2_dir_t *dir);
/// Filesystem-level filesystem operations
+// Find on-disk info about the filesystem
+//
+// Fills out the fsinfo structure based on the filesystem found on-disk.
+// Returns a negative error code on failure.
+int lfs2_fs_stat(lfs2_t *lfs2, struct lfs2_fsinfo *fsinfo);
+
// Finds the current size of the filesystem
//
// Note: Result is best effort. If files share COW structures, the returned
@@ -676,6 +712,40 @@ lfs2_ssize_t lfs2_fs_size(lfs2_t *lfs2);
// Returns a negative error code on failure.
int lfs2_fs_traverse(lfs2_t *lfs2, int (*cb)(void*, lfs2_block_t), void *data);
+// Attempt to proactively find free blocks
+//
+// Calling this function is not required, but may allowing the offloading of
+// the expensive block allocation scan to a less time-critical code path.
+//
+// Note: littlefs currently does not persist any found free blocks to disk.
+// This may change in the future.
+//
+// Returns a negative error code on failure. Finding no free blocks is
+// not an error.
+int lfs2_fs_gc(lfs2_t *lfs2);
+
+#ifndef LFS2_READONLY
+// Attempt to make the filesystem consistent and ready for writing
+//
+// Calling this function is not required, consistency will be implicitly
+// enforced on the first operation that writes to the filesystem, but this
+// function allows the work to be performed earlier and without other
+// filesystem changes.
+//
+// Returns a negative error code on failure.
+int lfs2_fs_mkconsistent(lfs2_t *lfs2);
+#endif
+
+#ifndef LFS2_READONLY
+// Grows the filesystem to a new size, updating the superblock with the new
+// block count.
+//
+// Note: This is irreversible.
+//
+// Returns a negative error code on failure.
+int lfs2_fs_grow(lfs2_t *lfs2, lfs2_size_t block_count);
+#endif
+
#ifndef LFS2_READONLY
#ifdef LFS2_MIGRATE
// Attempts to migrate a previous version of littlefs
diff --git a/lib/littlefs/lfs2_util.h b/lib/littlefs/lfs2_util.h
index 6a4c8ffb5..dd2cbcc10 100644
--- a/lib/littlefs/lfs2_util.h
+++ b/lib/littlefs/lfs2_util.h
@@ -167,10 +167,9 @@ static inline int lfs2_scmp(uint32_t a, uint32_t b) {
// Convert between 32-bit little-endian and native order
static inline uint32_t lfs2_fromle32(uint32_t a) {
-#if !defined(LFS2_NO_INTRINSICS) && ( \
- (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \
+#if (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \
(defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \
- (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
+ (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
return a;
#elif !defined(LFS2_NO_INTRINSICS) && ( \
(defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \
@@ -196,10 +195,9 @@ static inline uint32_t lfs2_frombe32(uint32_t a) {
(defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \
(defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
return __builtin_bswap32(a);
-#elif !defined(LFS2_NO_INTRINSICS) && ( \
- (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \
+#elif (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \
(defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \
- (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__))
+ (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
return a;
#else
return (((uint8_t*)&a)[0] << 24) |