forked from Imagelibrary/littlefs
Compare commits
8 Commits
v2.8.2
...
dhara-meta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4cde70dbee | ||
|
|
a8dce05bbc | ||
|
|
907b76fef3 | ||
|
|
a22f9afb03 | ||
|
|
1cda361df0 | ||
|
|
09fee81134 | ||
|
|
7535795a44 | ||
|
|
01a3b1f5f7 |
6
Makefile
6
Makefile
@@ -6,6 +6,7 @@ endif
|
|||||||
CC ?= gcc
|
CC ?= gcc
|
||||||
AR ?= ar
|
AR ?= ar
|
||||||
SIZE ?= size
|
SIZE ?= size
|
||||||
|
NM ?= nm
|
||||||
|
|
||||||
SRC += $(wildcard *.c bd/*.c)
|
SRC += $(wildcard *.c bd/*.c)
|
||||||
OBJ := $(SRC:.c=.o)
|
OBJ := $(SRC:.c=.o)
|
||||||
@@ -29,6 +30,7 @@ override CFLAGS += -Wextra -Wshadow -Wjump-misses-init -Wundef
|
|||||||
|
|
||||||
ifdef VERBOSE
|
ifdef VERBOSE
|
||||||
override TFLAGS += -v
|
override TFLAGS += -v
|
||||||
|
override SFLAGS += -v
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
||||||
@@ -39,6 +41,9 @@ asm: $(ASM)
|
|||||||
size: $(OBJ)
|
size: $(OBJ)
|
||||||
$(SIZE) -t $^
|
$(SIZE) -t $^
|
||||||
|
|
||||||
|
code_size:
|
||||||
|
./scripts/code_size.py $(SFLAGS)
|
||||||
|
|
||||||
test:
|
test:
|
||||||
./scripts/test.py $(TFLAGS)
|
./scripts/test.py $(TFLAGS)
|
||||||
.SECONDEXPANSION:
|
.SECONDEXPANSION:
|
||||||
@@ -65,3 +70,4 @@ clean:
|
|||||||
rm -f $(DEP)
|
rm -f $(DEP)
|
||||||
rm -f $(ASM)
|
rm -f $(ASM)
|
||||||
rm -f tests/*.toml.*
|
rm -f tests/*.toml.*
|
||||||
|
rm -f sizes/*
|
||||||
|
|||||||
@@ -80,11 +80,6 @@ int lfs_filebd_read(const struct lfs_config *cfg, lfs_block_t block,
|
|||||||
LFS_ASSERT(size % cfg->read_size == 0);
|
LFS_ASSERT(size % cfg->read_size == 0);
|
||||||
LFS_ASSERT(block < cfg->block_count);
|
LFS_ASSERT(block < cfg->block_count);
|
||||||
|
|
||||||
// zero for reproducability (in case file is truncated)
|
|
||||||
if (bd->cfg->erase_value != -1) {
|
|
||||||
memset(buffer, bd->cfg->erase_value, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
// read
|
// read
|
||||||
off_t res1 = lseek(bd->fd,
|
off_t res1 = lseek(bd->fd,
|
||||||
(off_t)block*cfg->block_size + (off_t)off, SEEK_SET);
|
(off_t)block*cfg->block_size + (off_t)off, SEEK_SET);
|
||||||
@@ -101,6 +96,11 @@ int lfs_filebd_read(const struct lfs_config *cfg, lfs_block_t block,
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// file truncated? zero for reproducability
|
||||||
|
if ((lfs_size_t)res2 < size) {
|
||||||
|
memset((uint8_t*)buffer + res2, 0, size-res2);
|
||||||
|
}
|
||||||
|
|
||||||
LFS_FILEBD_TRACE("lfs_filebd_read -> %d", 0);
|
LFS_FILEBD_TRACE("lfs_filebd_read -> %d", 0);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,11 +32,8 @@ int lfs_rambd_createcfg(const struct lfs_config *cfg,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// zero for reproducability?
|
// zero for reproducability (this matches filebd)
|
||||||
if (bd->cfg->erase_value != -1) {
|
memset(bd->buffer, 0, cfg->block_size * cfg->block_count);
|
||||||
memset(bd->buffer, bd->cfg->erase_value,
|
|
||||||
cfg->block_size * cfg->block_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
LFS_RAMBD_TRACE("lfs_rambd_createcfg -> %d", 0);
|
LFS_RAMBD_TRACE("lfs_rambd_createcfg -> %d", 0);
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
24
lfs.h
24
lfs.h
@@ -85,6 +85,7 @@ enum lfs_error {
|
|||||||
LFS_ERR_NOMEM = -12, // No more memory available
|
LFS_ERR_NOMEM = -12, // No more memory available
|
||||||
LFS_ERR_NOATTR = -61, // No data/attr available
|
LFS_ERR_NOATTR = -61, // No data/attr available
|
||||||
LFS_ERR_NAMETOOLONG = -36, // File name too long
|
LFS_ERR_NAMETOOLONG = -36, // File name too long
|
||||||
|
LFS_ERR_OVERFLOW = -75, // Value too large for defined data type
|
||||||
};
|
};
|
||||||
|
|
||||||
// File types
|
// File types
|
||||||
@@ -113,11 +114,20 @@ enum lfs_type {
|
|||||||
LFS_TYPE_SOFTTAIL = 0x600,
|
LFS_TYPE_SOFTTAIL = 0x600,
|
||||||
LFS_TYPE_HARDTAIL = 0x601,
|
LFS_TYPE_HARDTAIL = 0x601,
|
||||||
LFS_TYPE_MOVESTATE = 0x7ff,
|
LFS_TYPE_MOVESTATE = 0x7ff,
|
||||||
|
LFS_TYPE_COMMITCRC = 0x502,
|
||||||
|
LFS_TYPE_NPROGCRC = 0x5ff,
|
||||||
|
|
||||||
// internal chip sources
|
// internal chip sources
|
||||||
LFS_FROM_NOOP = 0x000,
|
LFS_FROM_NOOP = 0x000,
|
||||||
LFS_FROM_MOVE = 0x101,
|
LFS_FROM_MOVE = 0x101,
|
||||||
LFS_FROM_USERATTRS = 0x102,
|
LFS_FROM_USERATTRS = 0x102,
|
||||||
|
|
||||||
|
// internally used tag-types
|
||||||
|
LFS_T_CRC = 0xc,
|
||||||
|
LFS_T_USERATTR = 0x2,
|
||||||
|
|
||||||
|
// internally used tag-subtypes
|
||||||
|
LFS_S_NPROG = 0x01,
|
||||||
};
|
};
|
||||||
|
|
||||||
// File open flags
|
// File open flags
|
||||||
@@ -319,6 +329,20 @@ typedef struct lfs_cache {
|
|||||||
uint8_t *buffer;
|
uint8_t *buffer;
|
||||||
} lfs_cache_t;
|
} lfs_cache_t;
|
||||||
|
|
||||||
|
typedef struct lfs_mdir_ {
|
||||||
|
lfs_block_t pair[2];
|
||||||
|
uint32_t rev;
|
||||||
|
lfs_off_t roff;
|
||||||
|
lfs_off_t eoff;
|
||||||
|
uint16_t count;
|
||||||
|
|
||||||
|
// TODO need both erased and eperturb?
|
||||||
|
bool erased;
|
||||||
|
bool split;
|
||||||
|
bool eperturb;
|
||||||
|
lfs_block_t tail[2];
|
||||||
|
} lfs_mdir__t;
|
||||||
|
|
||||||
typedef struct lfs_mdir {
|
typedef struct lfs_mdir {
|
||||||
lfs_block_t pair[2];
|
lfs_block_t pair[2];
|
||||||
uint32_t rev;
|
uint32_t rev;
|
||||||
|
|||||||
328
scripts/code_size.py
Executable file
328
scripts/code_size.py
Executable file
@@ -0,0 +1,328 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
# This script finds the code size at the function level, with/without
|
||||||
|
# static functions, and has some conveniences for comparing different
|
||||||
|
# versions. It's basically one big wrapper around nm, and may or may
|
||||||
|
# not have been written out of jealousy of Linux's Bloat-O-Meter.
|
||||||
|
#
|
||||||
|
# Here's a useful bash script to use while developing:
|
||||||
|
# ./scripts/code_size.py -qo old.csv
|
||||||
|
# while true ; do ./code_scripts/size.py -d old.csv ; inotifywait -rqe modify * ; done
|
||||||
|
#
|
||||||
|
# Or even better, to automatically update results on commit:
|
||||||
|
# ./scripts/code_size.py -qo commit.csv
|
||||||
|
# while true ; do ./scripts/code_size.py -d commit.csv -o current.csv ; git diff --exit-code --quiet && cp current.csv commit.csv ; inotifywait -rqe modify * ; done
|
||||||
|
#
|
||||||
|
# Or my personal favorite:
|
||||||
|
# ./scripts/code_size.py -qo master.csv && cp master.csv commit.csv
|
||||||
|
# while true ; do ( ./scripts/code_size.py -i commit.csv -d master.csv -s ; ./scripts/code_size.py -i current.csv -d master.csv -s ; ./scripts/code_size.py -d master.csv -o current.csv -s ) | awk 'BEGIN {printf "%-16s %7s %7s %7s\n","","old","new","diff"} (NR==2 && $1="commit") || (NR==4 && $1="prev") || (NR==6 && $1="current") {printf "%-16s %7s %7s %7s %s\n",$1,$2,$3,$5,$6}' ; git diff --exit-code --quiet && cp current.csv commit.csv ; inotifywait -rqe modify * ; done
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
import itertools as it
|
||||||
|
import subprocess as sp
|
||||||
|
import shlex
|
||||||
|
import re
|
||||||
|
import csv
|
||||||
|
import collections as co
|
||||||
|
|
||||||
|
SIZEDIR = 'sizes'
|
||||||
|
RULES = """
|
||||||
|
define FLATTEN
|
||||||
|
%(sizedir)s/%(build)s.$(subst /,.,$(target)): $(target)
|
||||||
|
( echo "#line 1 \\"$$<\\"" ; %(cat)s $$< ) > $$@
|
||||||
|
%(sizedir)s/%(build)s.$(subst /,.,$(target:.c=.size)): \\
|
||||||
|
%(sizedir)s/%(build)s.$(subst /,.,$(target:.c=.o))
|
||||||
|
$(NM) --size-sort $$^ | sed 's/^/$(subst /,\\/,$(target:.c=.o)):/' > $$@
|
||||||
|
endef
|
||||||
|
$(foreach target,$(SRC),$(eval $(FLATTEN)))
|
||||||
|
|
||||||
|
-include %(sizedir)s/*.d
|
||||||
|
.SECONDARY:
|
||||||
|
|
||||||
|
%%.size: $(foreach t,$(subst /,.,$(SRC:.c=.size)),%%.$t)
|
||||||
|
cat $^ > $@
|
||||||
|
"""
|
||||||
|
CATS = {
|
||||||
|
'code': 'cat',
|
||||||
|
'code_inlined': 'sed \'s/^static\( inline\)\?//\'',
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(**args):
|
||||||
|
# mkdir -p sizedir
|
||||||
|
os.makedirs(args['sizedir'], exist_ok=True)
|
||||||
|
|
||||||
|
if args.get('inlined', False):
|
||||||
|
builds = ['code', 'code_inlined']
|
||||||
|
else:
|
||||||
|
builds = ['code']
|
||||||
|
|
||||||
|
# write makefiles for the different types of builds
|
||||||
|
makefiles = []
|
||||||
|
targets = []
|
||||||
|
for build in builds:
|
||||||
|
path = args['sizedir'] + '/' + build
|
||||||
|
with open(path + '.mk', 'w') as mk:
|
||||||
|
mk.write(RULES.replace(4*' ', '\t') % dict(
|
||||||
|
sizedir=args['sizedir'],
|
||||||
|
build=build,
|
||||||
|
cat=CATS[build]))
|
||||||
|
mk.write('\n')
|
||||||
|
|
||||||
|
# pass on defines
|
||||||
|
for d in args['D']:
|
||||||
|
mk.write('%s: override CFLAGS += -D%s\n' % (
|
||||||
|
path+'.size', d))
|
||||||
|
|
||||||
|
makefiles.append(path + '.mk')
|
||||||
|
targets.append(path + '.size')
|
||||||
|
|
||||||
|
# build in parallel
|
||||||
|
cmd = (['make', '-f', 'Makefile'] +
|
||||||
|
list(it.chain.from_iterable(['-f', m] for m in makefiles)) +
|
||||||
|
[target for target in targets])
|
||||||
|
if args.get('verbose', False):
|
||||||
|
print(' '.join(shlex.quote(c) for c in cmd))
|
||||||
|
proc = sp.Popen(cmd,
|
||||||
|
stdout=sp.DEVNULL if not args.get('verbose', False) else None)
|
||||||
|
proc.wait()
|
||||||
|
if proc.returncode != 0:
|
||||||
|
sys.exit(-1)
|
||||||
|
|
||||||
|
# find results
|
||||||
|
build_results = co.defaultdict(lambda: 0)
|
||||||
|
# notes
|
||||||
|
# - filters type
|
||||||
|
# - discards internal/debug functions (leading __)
|
||||||
|
pattern = re.compile(
|
||||||
|
'^(?P<file>[^:]+)' +
|
||||||
|
':(?P<size>[0-9a-fA-F]+)' +
|
||||||
|
' (?P<type>[%s])' % re.escape(args['type']) +
|
||||||
|
' (?!__)(?P<name>.+?)$')
|
||||||
|
for build in builds:
|
||||||
|
path = args['sizedir'] + '/' + build
|
||||||
|
with open(path + '.size') as size:
|
||||||
|
for line in size:
|
||||||
|
match = pattern.match(line)
|
||||||
|
if match:
|
||||||
|
file = match.group('file')
|
||||||
|
# discard .8449 suffixes created by optimizer
|
||||||
|
name = re.sub('\.[0-9]+', '', match.group('name'))
|
||||||
|
size = int(match.group('size'), 16)
|
||||||
|
build_results[(build, file, name)] += size
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for (build, file, name), size in build_results.items():
|
||||||
|
if build == 'code':
|
||||||
|
results.append((file, name, size, False))
|
||||||
|
elif (build == 'code_inlined' and
|
||||||
|
('inlined', file, name) not in results):
|
||||||
|
results.append((file, name, size, True))
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
def main(**args):
|
||||||
|
# find results
|
||||||
|
if not args.get('input', None):
|
||||||
|
results = build(**args)
|
||||||
|
else:
|
||||||
|
with open(args['input']) as f:
|
||||||
|
r = csv.DictReader(f)
|
||||||
|
results = [
|
||||||
|
( result['file'],
|
||||||
|
result['name'],
|
||||||
|
int(result['size']),
|
||||||
|
bool(int(result.get('inlined', 0))))
|
||||||
|
for result in r
|
||||||
|
if (not bool(int(result.get('inlined', 0))) or
|
||||||
|
args.get('inlined', False))]
|
||||||
|
|
||||||
|
total = 0
|
||||||
|
for _, _, size, inlined in results:
|
||||||
|
if not inlined:
|
||||||
|
total += size
|
||||||
|
|
||||||
|
# find previous results?
|
||||||
|
if args.get('diff', None):
|
||||||
|
with open(args['diff']) as f:
|
||||||
|
r = csv.DictReader(f)
|
||||||
|
prev_results = [
|
||||||
|
( result['file'],
|
||||||
|
result['name'],
|
||||||
|
int(result['size']),
|
||||||
|
bool(int(result.get('inlined', 0))))
|
||||||
|
for result in r
|
||||||
|
if (not bool(int(result.get('inlined', 0))) or
|
||||||
|
args.get('inlined', False))]
|
||||||
|
|
||||||
|
prev_total = 0
|
||||||
|
for _, _, size, inlined in prev_results:
|
||||||
|
if not inlined:
|
||||||
|
prev_total += size
|
||||||
|
|
||||||
|
# write results to CSV
|
||||||
|
if args.get('output', None):
|
||||||
|
results.sort(key=lambda x: (-x[2], x))
|
||||||
|
with open(args['output'], 'w') as f:
|
||||||
|
w = csv.writer(f)
|
||||||
|
if args.get('inlined', False):
|
||||||
|
w.writerow(['file', 'name', 'size', 'inlined'])
|
||||||
|
for file, name, size, inlined in results:
|
||||||
|
w.writerow((file, name, size, int(inlined)))
|
||||||
|
else:
|
||||||
|
w.writerow(['file', 'name', 'size'])
|
||||||
|
for file, name, size, inlined in results:
|
||||||
|
w.writerow((file, name, size))
|
||||||
|
|
||||||
|
# print results
|
||||||
|
def dedup_functions(results):
|
||||||
|
functions = co.defaultdict(lambda: (0, True))
|
||||||
|
for _, name, size, inlined in results:
|
||||||
|
if not inlined:
|
||||||
|
functions[name] = (functions[name][0] + size, False)
|
||||||
|
for _, name, size, inlined in results:
|
||||||
|
if inlined and functions[name][1]:
|
||||||
|
functions[name] = (functions[name][0] + size, True)
|
||||||
|
return functions
|
||||||
|
|
||||||
|
def dedup_files(results):
|
||||||
|
files = co.defaultdict(lambda: 0)
|
||||||
|
for file, _, size, inlined in results:
|
||||||
|
if not inlined:
|
||||||
|
files[file] += size
|
||||||
|
return files
|
||||||
|
|
||||||
|
def diff_sizes(olds, news):
|
||||||
|
diff = co.defaultdict(lambda: (None, None, None))
|
||||||
|
for name, new in news.items():
|
||||||
|
diff[name] = (None, new, new)
|
||||||
|
for name, old in olds.items():
|
||||||
|
new = diff[name][1] or 0
|
||||||
|
diff[name] = (old, new, new-old)
|
||||||
|
return diff
|
||||||
|
|
||||||
|
def print_header(name=''):
|
||||||
|
if not args.get('diff', False):
|
||||||
|
print('%-40s %7s' % (name, 'size'))
|
||||||
|
else:
|
||||||
|
print('%-40s %7s %7s %7s' % (name, 'old', 'new', 'diff'))
|
||||||
|
|
||||||
|
def print_functions():
|
||||||
|
functions = dedup_functions(results)
|
||||||
|
functions = {
|
||||||
|
name+' (inlined)' if inlined else name: size
|
||||||
|
for name, (size, inlined) in functions.items()}
|
||||||
|
|
||||||
|
if not args.get('diff', None):
|
||||||
|
print_header('function')
|
||||||
|
for name, size in sorted(functions.items(),
|
||||||
|
key=lambda x: (-x[1], x)):
|
||||||
|
print("%-40s %7d" % (name, size))
|
||||||
|
else:
|
||||||
|
prev_functions = dedup_functions(prev_results)
|
||||||
|
prev_functions = {
|
||||||
|
name+' (inlined)' if inlined else name: size
|
||||||
|
for name, (size, inlined) in prev_functions.items()}
|
||||||
|
diff = diff_sizes(functions, prev_functions)
|
||||||
|
print_header('function (%d added, %d removed)' % (
|
||||||
|
sum(1 for old, _, _ in diff.values() if not old),
|
||||||
|
sum(1 for _, new, _ in diff.values() if not new)))
|
||||||
|
for name, (old, new, diff) in sorted(diff.items(),
|
||||||
|
key=lambda x: (-(x[1][2] or 0), x)):
|
||||||
|
if diff or args.get('all', False):
|
||||||
|
print("%-40s %7s %7s %+7d%s" % (
|
||||||
|
name, old or "-", new or "-", diff,
|
||||||
|
' (%+.2f%%)' % (100*((new-old)/old))
|
||||||
|
if old and new else
|
||||||
|
''))
|
||||||
|
|
||||||
|
def print_files():
|
||||||
|
files = dedup_files(results)
|
||||||
|
|
||||||
|
if not args.get('diff', None):
|
||||||
|
print_header('file')
|
||||||
|
for file, size in sorted(files.items(),
|
||||||
|
key=lambda x: (-x[1], x)):
|
||||||
|
print("%-40s %7d" % (file, size))
|
||||||
|
else:
|
||||||
|
prev_files = dedup_files(prev_results)
|
||||||
|
diff = diff_sizes(files, prev_files)
|
||||||
|
print_header('file (%d added, %d removed)' % (
|
||||||
|
sum(1 for old, _, _ in diff.values() if not old),
|
||||||
|
sum(1 for _, new, _ in diff.values() if not new)))
|
||||||
|
for name, (old, new, diff) in sorted(diff.items(),
|
||||||
|
key=lambda x: (-(x[1][2] or 0), x)):
|
||||||
|
if diff or args.get('all', False):
|
||||||
|
print("%-40s %7s %7s %+7d%s" % (
|
||||||
|
name, old or "-", new or "-", diff,
|
||||||
|
' (%+.2f%%)' % (100*((new-old)/old))
|
||||||
|
if old and new else
|
||||||
|
''))
|
||||||
|
|
||||||
|
def print_totals():
|
||||||
|
if not args.get('diff', None):
|
||||||
|
print("%-40s %7d" % ('TOTALS', total))
|
||||||
|
else:
|
||||||
|
print("%-40s %7s %7s %+7d%s" % (
|
||||||
|
'TOTALS', prev_total, total, total-prev_total,
|
||||||
|
' (%+.2f%%)' % (100*((total-prev_total)/total))
|
||||||
|
if prev_total and total else
|
||||||
|
''))
|
||||||
|
|
||||||
|
def print_status():
|
||||||
|
if not args.get('diff', None):
|
||||||
|
print(total)
|
||||||
|
else:
|
||||||
|
print("%d (%+.2f%%)" % (total, 100*((total-prev_total)/total)))
|
||||||
|
|
||||||
|
if args.get('quiet', False):
|
||||||
|
pass
|
||||||
|
elif args.get('status', False):
|
||||||
|
print_status()
|
||||||
|
elif args.get('summary', False):
|
||||||
|
print_header()
|
||||||
|
print_totals()
|
||||||
|
elif args.get('files', False):
|
||||||
|
print_files()
|
||||||
|
print_totals()
|
||||||
|
else:
|
||||||
|
print_functions()
|
||||||
|
print_totals()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Find code size at the function level.")
|
||||||
|
parser.add_argument('sizedir', nargs='?', default=SIZEDIR,
|
||||||
|
help="Directory to store intermediary results. Defaults "
|
||||||
|
"to \"%s\"." % SIZEDIR)
|
||||||
|
parser.add_argument('-D', action='append', default=[],
|
||||||
|
help="Specify compile-time define.")
|
||||||
|
parser.add_argument('-v', '--verbose', action='store_true',
|
||||||
|
help="Output commands that run behind the scenes.")
|
||||||
|
parser.add_argument('-i', '--input',
|
||||||
|
help="Don't compile and find code sizes, instead use this CSV file.")
|
||||||
|
parser.add_argument('-o', '--output',
|
||||||
|
help="Specify CSV file to store results.")
|
||||||
|
parser.add_argument('-d', '--diff',
|
||||||
|
help="Specify CSV file to diff code size against.")
|
||||||
|
parser.add_argument('-a', '--all', action='store_true',
|
||||||
|
help="Show all functions, not just the ones that changed.")
|
||||||
|
parser.add_argument('--inlined', action='store_true',
|
||||||
|
help="Run a second compilation to find the sizes of functions normally "
|
||||||
|
"removed by optimizations. These will be shown as \"*.inlined\" "
|
||||||
|
"functions, and will not be included in the total.")
|
||||||
|
parser.add_argument('--files', action='store_true',
|
||||||
|
help="Show file-level code sizes. Note this does not include padding! "
|
||||||
|
"So sizes may differ from other tools.")
|
||||||
|
parser.add_argument('-s', '--summary', action='store_true',
|
||||||
|
help="Only show the total code size.")
|
||||||
|
parser.add_argument('-S', '--status', action='store_true',
|
||||||
|
help="Show minimum info useful for a single-line status.")
|
||||||
|
parser.add_argument('-q', '--quiet', action='store_true',
|
||||||
|
help="Don't show anything, useful with -o.")
|
||||||
|
parser.add_argument('--type', default='tTrRdDbB',
|
||||||
|
help="Type of symbols to report, this uses the same single-character "
|
||||||
|
"type-names emitted by nm. Defaults to %(default)r.")
|
||||||
|
sys.exit(main(**vars(parser.parse_args())))
|
||||||
@@ -24,6 +24,7 @@ TAG_TYPES = {
|
|||||||
'gstate': (0x700, 0x700),
|
'gstate': (0x700, 0x700),
|
||||||
'movestate': (0x7ff, 0x7ff),
|
'movestate': (0x7ff, 0x7ff),
|
||||||
'crc': (0x700, 0x500),
|
'crc': (0x700, 0x500),
|
||||||
|
'nprogcrc': (0x7ff, 0x5ff),
|
||||||
}
|
}
|
||||||
|
|
||||||
class Tag:
|
class Tag:
|
||||||
@@ -99,7 +100,16 @@ class Tag:
|
|||||||
return struct.unpack('b', struct.pack('B', self.chunk))[0]
|
return struct.unpack('b', struct.pack('B', self.chunk))[0]
|
||||||
|
|
||||||
def is_(self, type):
|
def is_(self, type):
|
||||||
return (self.type & TAG_TYPES[type][0]) == TAG_TYPES[type][1]
|
try:
|
||||||
|
if ' ' in type:
|
||||||
|
type1, type3 = type.split()
|
||||||
|
return (self.is_(type1) and
|
||||||
|
(self.type & ~TAG_TYPES[type1][0]) == int(type3, 0))
|
||||||
|
|
||||||
|
return self.type == int(type, 0)
|
||||||
|
|
||||||
|
except (ValueError, KeyError):
|
||||||
|
return (self.type & TAG_TYPES[type][0]) == TAG_TYPES[type][1]
|
||||||
|
|
||||||
def mkmask(self):
|
def mkmask(self):
|
||||||
return Tag(
|
return Tag(
|
||||||
@@ -109,14 +119,20 @@ class Tag:
|
|||||||
|
|
||||||
def chid(self, nid):
|
def chid(self, nid):
|
||||||
ntag = Tag(self.type, nid, self.size)
|
ntag = Tag(self.type, nid, self.size)
|
||||||
if hasattr(self, 'off'): ntag.off = self.off
|
if hasattr(self, 'off'): ntag.off = self.off
|
||||||
if hasattr(self, 'data'): ntag.data = self.data
|
if hasattr(self, 'data'): ntag.data = self.data
|
||||||
if hasattr(self, 'crc'): ntag.crc = self.crc
|
if hasattr(self, 'crc'): ntag.crc = self.crc
|
||||||
|
if hasattr(self, 'erased'): ntag.erased = self.erased
|
||||||
return ntag
|
return ntag
|
||||||
|
|
||||||
def typerepr(self):
|
def typerepr(self):
|
||||||
if self.is_('crc') and getattr(self, 'crc', 0xffffffff) != 0xffffffff:
|
if (self.is_('crc') and not self.is_('nprogcrc') and
|
||||||
return 'crc (bad)'
|
getattr(self, 'crc', 0xffffffff) != 0xffffffff):
|
||||||
|
crc_status = ' (bad)'
|
||||||
|
elif self.is_('nprogcrc') and getattr(self, 'erased', False):
|
||||||
|
crc_status = ' (era)'
|
||||||
|
else:
|
||||||
|
crc_status = ''
|
||||||
|
|
||||||
reverse_types = {v: k for k, v in TAG_TYPES.items()}
|
reverse_types = {v: k for k, v in TAG_TYPES.items()}
|
||||||
for prefix in range(12):
|
for prefix in range(12):
|
||||||
@@ -124,12 +140,12 @@ class Tag:
|
|||||||
if (mask, self.type & mask) in reverse_types:
|
if (mask, self.type & mask) in reverse_types:
|
||||||
type = reverse_types[mask, self.type & mask]
|
type = reverse_types[mask, self.type & mask]
|
||||||
if prefix > 0:
|
if prefix > 0:
|
||||||
return '%s %#0*x' % (
|
return '%s %#x%s' % (
|
||||||
type, prefix//4, self.type & ((1 << prefix)-1))
|
type, self.type & ((1 << prefix)-1), crc_status)
|
||||||
else:
|
else:
|
||||||
return type
|
return '%s%s' % (type, crc_status)
|
||||||
else:
|
else:
|
||||||
return '%02x' % self.type
|
return '%02x%s' % (self.type, crc_status)
|
||||||
|
|
||||||
def idrepr(self):
|
def idrepr(self):
|
||||||
return repr(self.id) if self.id != 0x3ff else '.'
|
return repr(self.id) if self.id != 0x3ff else '.'
|
||||||
@@ -172,6 +188,8 @@ class MetadataPair:
|
|||||||
|
|
||||||
self.rev, = struct.unpack('<I', block[0:4])
|
self.rev, = struct.unpack('<I', block[0:4])
|
||||||
crc = binascii.crc32(block[0:4])
|
crc = binascii.crc32(block[0:4])
|
||||||
|
etag = None
|
||||||
|
estate = None
|
||||||
|
|
||||||
# parse tags
|
# parse tags
|
||||||
corrupt = False
|
corrupt = False
|
||||||
@@ -182,11 +200,11 @@ class MetadataPair:
|
|||||||
while len(block) - off >= 4:
|
while len(block) - off >= 4:
|
||||||
ntag, = struct.unpack('>I', block[off:off+4])
|
ntag, = struct.unpack('>I', block[off:off+4])
|
||||||
|
|
||||||
tag = Tag(int(tag) ^ ntag)
|
tag = Tag((int(tag) ^ ntag) & 0x7fffffff)
|
||||||
tag.off = off + 4
|
tag.off = off + 4
|
||||||
tag.data = block[off+4:off+tag.dsize]
|
tag.data = block[off+4:off+tag.dsize]
|
||||||
if tag.is_('crc'):
|
if tag.is_('crc') and not tag.is_('nprogcrc'):
|
||||||
crc = binascii.crc32(block[off:off+4+4], crc)
|
crc = binascii.crc32(block[off:off+2*4], crc)
|
||||||
else:
|
else:
|
||||||
crc = binascii.crc32(block[off:off+tag.dsize], crc)
|
crc = binascii.crc32(block[off:off+tag.dsize], crc)
|
||||||
tag.crc = crc
|
tag.crc = crc
|
||||||
@@ -194,16 +212,29 @@ class MetadataPair:
|
|||||||
|
|
||||||
self.all_.append(tag)
|
self.all_.append(tag)
|
||||||
|
|
||||||
if tag.is_('crc'):
|
if tag.is_('nprogcrc') and len(tag.data) == 8:
|
||||||
|
etag = tag
|
||||||
|
estate = struct.unpack('<II', tag.data)
|
||||||
|
elif tag.is_('crc'):
|
||||||
# is valid commit?
|
# is valid commit?
|
||||||
if crc != 0xffffffff:
|
if crc != 0xffffffff:
|
||||||
corrupt = True
|
corrupt = True
|
||||||
if not corrupt:
|
if not corrupt:
|
||||||
self.log = self.all_.copy()
|
self.log = self.all_.copy()
|
||||||
|
# end of commit?
|
||||||
|
if estate:
|
||||||
|
esize, ecrc = estate
|
||||||
|
dcrc = 0xffffffff ^ binascii.crc32(block[off:off+esize])
|
||||||
|
if ecrc == dcrc:
|
||||||
|
etag.erased = True
|
||||||
|
corrupt = True
|
||||||
|
elif not (tag.is_('crc 0x0') or tag.is_('crc 0x1')):
|
||||||
|
corrupt = True
|
||||||
|
|
||||||
# reset tag parsing
|
# reset tag parsing
|
||||||
crc = 0
|
crc = 0
|
||||||
tag = Tag(int(tag) ^ ((tag.type & 1) << 31))
|
etag = None
|
||||||
|
estate = None
|
||||||
|
|
||||||
# find active ids
|
# find active ids
|
||||||
self.ids = list(it.takewhile(
|
self.ids = list(it.takewhile(
|
||||||
@@ -280,7 +311,7 @@ class MetadataPair:
|
|||||||
f.write('\n')
|
f.write('\n')
|
||||||
|
|
||||||
for tag in tags:
|
for tag in tags:
|
||||||
f.write("%08x: %08x %-13s %4s %4s" % (
|
f.write("%08x: %08x %-14s %3s %4s" % (
|
||||||
tag.off, tag,
|
tag.off, tag,
|
||||||
tag.typerepr(), tag.idrepr(), tag.sizerepr()))
|
tag.typerepr(), tag.idrepr(), tag.sizerepr()))
|
||||||
if truncate:
|
if truncate:
|
||||||
|
|||||||
51
tests/test_mdirs.toml
Normal file
51
tests/test_mdirs.toml
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Test internal operations of metadata-pairs
|
||||||
|
|
||||||
|
[[case]]
|
||||||
|
in = "lfs.c"
|
||||||
|
code = '''
|
||||||
|
lfs_init(&lfs, &cfg) => 0;
|
||||||
|
lfs_mdir__t mdir = {
|
||||||
|
.pair = {0, 1},
|
||||||
|
.rev = 0,
|
||||||
|
.roff = 0,
|
||||||
|
.eoff = 0,
|
||||||
|
.count = 0,
|
||||||
|
.split = false,
|
||||||
|
.eperturb = false,
|
||||||
|
.tail = {0xffffffff, 0xffffffff},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct lfs_mattr_ attrs[] = {
|
||||||
|
{LFS_MKTAG_(LFS_T_USERATTR, 0, 0), "0000", 4},
|
||||||
|
{LFS_MKTAG_(LFS_T_USERATTR, 1, 0), "0001", 4},
|
||||||
|
{LFS_MKTAG_(LFS_T_USERATTR, 2, 0), "0002", 4},
|
||||||
|
{LFS_MKTAG_(LFS_T_USERATTR, 3, 0), "0003", 4},
|
||||||
|
{LFS_MKTAG_(LFS_T_USERATTR, 4, 0), "0004", 4},
|
||||||
|
{LFS_MKTAG_(LFS_T_USERATTR, 5, 0), "0005", 4},
|
||||||
|
{LFS_MKTAG_(LFS_T_USERATTR, 6, 0), "0006", 4},
|
||||||
|
{LFS_MKTAG_(LFS_T_USERATTR, 7, 0), "0007", 4},
|
||||||
|
{LFS_MKTAG_(LFS_T_USERATTR, 8, 0), "0008", 4},
|
||||||
|
{LFS_MKTAG_(LFS_T_USERATTR, 9, 0), "0009", 4},
|
||||||
|
{LFS_MKTAG_(LFS_T_USERATTR, 10, 0), "0010", 4},
|
||||||
|
{LFS_MKTAG_(LFS_T_USERATTR, 11, 0), "0011", 4},
|
||||||
|
{LFS_MKTAG_(LFS_T_USERATTR, 12, 0), "0012", 4},
|
||||||
|
{LFS_MKTAG_(LFS_T_USERATTR, 13, 0), "0013", 4},
|
||||||
|
{LFS_MKTAG_(LFS_T_USERATTR, 14, 0), "0014", 4},
|
||||||
|
{LFS_MKTAG_(LFS_T_USERATTR, 15, 0), "0015", 4},
|
||||||
|
{LFS_MKTAG_(LFS_T_USERATTR, 16, 0), "0016", 4}
|
||||||
|
};
|
||||||
|
lfs_size_t attr_count = sizeof(attrs)/sizeof(attrs[0]);
|
||||||
|
|
||||||
|
lfs_dir_compact_(&lfs, &mdir, attrs, attr_count) => 0;
|
||||||
|
|
||||||
|
for (lfs_size_t i = 0; i < attr_count; i++) {
|
||||||
|
printf("looking up %08x\n", attrs[i].tag);
|
||||||
|
lfs_size_t nsize;
|
||||||
|
lfs_dir_getattr_(&lfs, &mdir,
|
||||||
|
attrs[i].tag,
|
||||||
|
buffer, sizeof(buffer), &nsize) => attrs[i].tag;
|
||||||
|
assert(nsize == attrs[i].size);
|
||||||
|
assert(memcmp(buffer, attrs[i].buffer, attrs[i].size) == 0);
|
||||||
|
}
|
||||||
|
'''
|
||||||
172
tests/test_powerloss.toml
Normal file
172
tests/test_powerloss.toml
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
# There are already a number of tests that test general operations under
|
||||||
|
# power-loss (see the reentrant attribute). These tests are for explicitly
|
||||||
|
# testing specific corner cases.
|
||||||
|
|
||||||
|
[[case]] # only a revision count
|
||||||
|
code = '''
|
||||||
|
lfs_format(&lfs, &cfg) => 0;
|
||||||
|
|
||||||
|
lfs_mount(&lfs, &cfg) => 0;
|
||||||
|
lfs_mkdir(&lfs, "notebook") => 0;
|
||||||
|
lfs_file_open(&lfs, &file, "notebook/paper",
|
||||||
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
|
||||||
|
strcpy((char*)buffer, "hello");
|
||||||
|
size = strlen("hello");
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
lfs_file_write(&lfs, &file, buffer, size) => size;
|
||||||
|
lfs_file_sync(&lfs, &file) => 0;
|
||||||
|
}
|
||||||
|
lfs_file_close(&lfs, &file) => 0;
|
||||||
|
|
||||||
|
char rbuffer[256];
|
||||||
|
lfs_file_open(&lfs, &file, "notebook/paper", LFS_O_RDONLY) => 0;
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
lfs_file_read(&lfs, &file, rbuffer, size) => size;
|
||||||
|
assert(memcmp(rbuffer, buffer, size) == 0);
|
||||||
|
}
|
||||||
|
lfs_file_close(&lfs, &file) => 0;
|
||||||
|
lfs_unmount(&lfs) => 0;
|
||||||
|
|
||||||
|
// get pair/rev count
|
||||||
|
lfs_mount(&lfs, &cfg) => 0;
|
||||||
|
lfs_dir_open(&lfs, &dir, "notebook") => 0;
|
||||||
|
lfs_block_t pair[2] = {dir.m.pair[0], dir.m.pair[1]};
|
||||||
|
uint32_t rev = dir.m.rev;
|
||||||
|
lfs_dir_close(&lfs, &dir) => 0;
|
||||||
|
lfs_unmount(&lfs) => 0;
|
||||||
|
|
||||||
|
// write just the revision count
|
||||||
|
uint8_t bbuffer[LFS_BLOCK_SIZE];
|
||||||
|
cfg.read(&cfg, pair[1], 0, bbuffer, LFS_BLOCK_SIZE) => 0;
|
||||||
|
|
||||||
|
memcpy(bbuffer, &(uint32_t){lfs_tole32(rev+1)}, sizeof(uint32_t));
|
||||||
|
|
||||||
|
cfg.erase(&cfg, pair[1]) => 0;
|
||||||
|
cfg.prog(&cfg, pair[1], 0, bbuffer, LFS_BLOCK_SIZE) => 0;
|
||||||
|
|
||||||
|
lfs_mount(&lfs, &cfg) => 0;
|
||||||
|
|
||||||
|
// can read?
|
||||||
|
lfs_file_open(&lfs, &file, "notebook/paper", LFS_O_RDONLY) => 0;
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
lfs_file_read(&lfs, &file, rbuffer, size) => size;
|
||||||
|
assert(memcmp(rbuffer, buffer, size) == 0);
|
||||||
|
}
|
||||||
|
lfs_file_close(&lfs, &file) => 0;
|
||||||
|
|
||||||
|
// can write?
|
||||||
|
lfs_file_open(&lfs, &file, "notebook/paper",
|
||||||
|
LFS_O_WRONLY | LFS_O_APPEND) => 0;
|
||||||
|
strcpy((char*)buffer, "goodbye");
|
||||||
|
size = strlen("goodbye");
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
lfs_file_write(&lfs, &file, buffer, size) => size;
|
||||||
|
lfs_file_sync(&lfs, &file) => 0;
|
||||||
|
}
|
||||||
|
lfs_file_close(&lfs, &file) => 0;
|
||||||
|
|
||||||
|
lfs_file_open(&lfs, &file, "notebook/paper", LFS_O_RDONLY) => 0;
|
||||||
|
strcpy((char*)buffer, "hello");
|
||||||
|
size = strlen("hello");
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
lfs_file_read(&lfs, &file, rbuffer, size) => size;
|
||||||
|
assert(memcmp(rbuffer, buffer, size) == 0);
|
||||||
|
}
|
||||||
|
strcpy((char*)buffer, "goodbye");
|
||||||
|
size = strlen("goodbye");
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
lfs_file_read(&lfs, &file, rbuffer, size) => size;
|
||||||
|
assert(memcmp(rbuffer, buffer, size) == 0);
|
||||||
|
}
|
||||||
|
lfs_file_close(&lfs, &file) => 0;
|
||||||
|
|
||||||
|
lfs_unmount(&lfs) => 0;
|
||||||
|
'''
|
||||||
|
|
||||||
|
[[case]] # partial prog, may not be byte in order!
|
||||||
|
if = "LFS_PROG_SIZE < LFS_BLOCK_SIZE"
|
||||||
|
define.BYTE_OFF = ["0", "LFS_PROG_SIZE-1", "LFS_PROG_SIZE/2"]
|
||||||
|
define.BYTE_VALUE = [0x33, 0xcc]
|
||||||
|
in = "lfs.c"
|
||||||
|
code = '''
|
||||||
|
lfs_format(&lfs, &cfg) => 0;
|
||||||
|
|
||||||
|
lfs_mount(&lfs, &cfg) => 0;
|
||||||
|
lfs_mkdir(&lfs, "notebook") => 0;
|
||||||
|
lfs_file_open(&lfs, &file, "notebook/paper",
|
||||||
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
|
||||||
|
strcpy((char*)buffer, "hello");
|
||||||
|
size = strlen("hello");
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
lfs_file_write(&lfs, &file, buffer, size) => size;
|
||||||
|
lfs_file_sync(&lfs, &file) => 0;
|
||||||
|
}
|
||||||
|
lfs_file_close(&lfs, &file) => 0;
|
||||||
|
|
||||||
|
char rbuffer[256];
|
||||||
|
lfs_file_open(&lfs, &file, "notebook/paper", LFS_O_RDONLY) => 0;
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
lfs_file_read(&lfs, &file, rbuffer, size) => size;
|
||||||
|
assert(memcmp(rbuffer, buffer, size) == 0);
|
||||||
|
}
|
||||||
|
lfs_file_close(&lfs, &file) => 0;
|
||||||
|
lfs_unmount(&lfs) => 0;
|
||||||
|
|
||||||
|
// imitate a partial prog, value should not matter, if littlefs
|
||||||
|
// doesn't notice the partial prog testbd will assert
|
||||||
|
|
||||||
|
// get offset to next prog
|
||||||
|
lfs_mount(&lfs, &cfg) => 0;
|
||||||
|
lfs_dir_open(&lfs, &dir, "notebook") => 0;
|
||||||
|
lfs_block_t block = dir.m.pair[0];
|
||||||
|
lfs_off_t off = dir.m.off;
|
||||||
|
lfs_dir_close(&lfs, &dir) => 0;
|
||||||
|
lfs_unmount(&lfs) => 0;
|
||||||
|
|
||||||
|
// tweak byte
|
||||||
|
uint8_t bbuffer[LFS_BLOCK_SIZE];
|
||||||
|
cfg.read(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
|
||||||
|
|
||||||
|
bbuffer[off + BYTE_OFF] = BYTE_VALUE;
|
||||||
|
|
||||||
|
cfg.erase(&cfg, block) => 0;
|
||||||
|
cfg.prog(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
|
||||||
|
|
||||||
|
lfs_mount(&lfs, &cfg) => 0;
|
||||||
|
|
||||||
|
// can read?
|
||||||
|
lfs_file_open(&lfs, &file, "notebook/paper", LFS_O_RDONLY) => 0;
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
lfs_file_read(&lfs, &file, rbuffer, size) => size;
|
||||||
|
assert(memcmp(rbuffer, buffer, size) == 0);
|
||||||
|
}
|
||||||
|
lfs_file_close(&lfs, &file) => 0;
|
||||||
|
|
||||||
|
// can write?
|
||||||
|
lfs_file_open(&lfs, &file, "notebook/paper",
|
||||||
|
LFS_O_WRONLY | LFS_O_APPEND) => 0;
|
||||||
|
strcpy((char*)buffer, "goodbye");
|
||||||
|
size = strlen("goodbye");
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
lfs_file_write(&lfs, &file, buffer, size) => size;
|
||||||
|
lfs_file_sync(&lfs, &file) => 0;
|
||||||
|
}
|
||||||
|
lfs_file_close(&lfs, &file) => 0;
|
||||||
|
|
||||||
|
lfs_file_open(&lfs, &file, "notebook/paper", LFS_O_RDONLY) => 0;
|
||||||
|
strcpy((char*)buffer, "hello");
|
||||||
|
size = strlen("hello");
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
lfs_file_read(&lfs, &file, rbuffer, size) => size;
|
||||||
|
assert(memcmp(rbuffer, buffer, size) == 0);
|
||||||
|
}
|
||||||
|
strcpy((char*)buffer, "goodbye");
|
||||||
|
size = strlen("goodbye");
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
lfs_file_read(&lfs, &file, rbuffer, size) => size;
|
||||||
|
assert(memcmp(rbuffer, buffer, size) == 0);
|
||||||
|
}
|
||||||
|
lfs_file_close(&lfs, &file) => 0;
|
||||||
|
|
||||||
|
lfs_unmount(&lfs) => 0;
|
||||||
|
'''
|
||||||
Reference in New Issue
Block a user