Instead of 1. defining LFS_STATICCFG and 2. defining all LFS_READ_SIZE,
LFS_PROG_SIZE, etc. Configuration can now be made static by defining
LFS_READ_SIZE, LFS_PROG_SIZE, etc. Thanks to a really large ifdef, if
all configurations are provided, LFS_STATICCFG will be defined and the
RAM cost fully removed.
Additionally, we can remove the ROM cost of each struct cfg member,
allowing code savings when config is only partially defined, which is
perhaps more common.
This moves all of the configuration logic in lfs.h, which has the nice
side effect of keeping all of the decision making in the same location.
The only catch is that we need to differentiate between the cfg->*_max
and *_MAX limits. To do this I've renamed the configuration *_max to
*_limit. Note that these two are slightly different, with *_max
indicating the maximum supported by the current driver, and *_limit
the maximum supported by the specific instance of littlefs. However if
you do define a *_LIMIT, I've added an override for the relevant *_MAX,
since I can't think of a time where you _wouldn't_ want to do that.
---
This also required some tweaks in scripts/test.py in order to populate
the lfs_cfg struct correctly. This happens since the test defines
overlap littlefs's configuration defines. This does change what is being
tested a bit, but hopefully that's not a real issue.
Suggested by tim-nordell-nimbelink
This makes littlefs's usage of the term "cache" an entirely internal
concept and hopefully avoids some confusion about the usefulness of
throwing RAM > block_size at these buffers.
The term cache isn't entirely inaccurate, these buffers do act as
single-line caches, however more often the term cache is used to
describe multi-line caches. Maybe this will be added in littlefs's
future, but the code-size cost makes this change not worth the overhead
at the moment.
As an embedded library, littlefs's configuration straddles two worlds.
In most cases the configuration is usually constant at build time, but
when integrated into OSs, the configuration needs to be dynamically
configurable.
To help with this, littlefs has a separate lfs_config struct that can be
placed into ROM when possible.
But you know what's better than ROM configuration? Truely inlinable
static configuration known at compile-time. In addition to avoiding the
RAM cost, compile-time configuration allows for additional compiler
optimizations, such as constexpr-elimination and removal of unused
code-paths.
So how to enable static configuration?
1. define LFS_STATICCFG
2. implement callbacks as global functions:
- lfs_read
- lfs_prog
- lfs_erase
- lfs_sync
2. define the now-required constants that configure littlefs:
- LFS_READ_SIZE
- LFS_PROG_SIZE
- LFS_BLOCK_SIZE
- LFS_BLOCK_COUNT
- LFS_BLOCK_CYCLES
- LFS_CACHE_SIZE
- LFS_LOOKAHEAD_SIZE
- LFS_READ_BUFFER (optional)
- LFS_PROG_BUFFER (optional)
- LFS_LOOKAHEAD_BUFFER (optional)
- LFS_NAME_MAX (optional)
- LFS_FILE_MAX (optional)
- LFS_ATTR_MAX (optional)
Note, there is a separate configuration for the file configuration, this
can be enabled/disabled independently of LFS_STATICCFG. You will likely
want to define this as well if you are looking for the smallest code
size.
In order to avoid a mess of #ifdefs, the internals of littlefs use a
simple macro that redirects to either the dynamic or static config at
compile time:
#ifdef LFS_STATICCFG
#define LFS_CFG_READ_SIZE(lfs) ((void)lfs, LFS_READ_SIZE)
#else
#define LFS_CFG_READ_SIZE(lfs) lfs->cfg->read_size
#endif
Unfortunately it does look like there still may be a lot of issues
related to warnings of comparisons against constants... If only C had
a way to ignore warnings on individual statements...
Original idea by apmorton
Moved .travis.yml over to use the new test framework. A part of this
involved testing all of the configurations ran on the old framework
and deciding which to carry over. The new framework duplicates some of
the cases tested by the configurations so some configurations could be
dropped.
The .travis.yml includes some extreme ones, such as no inline files,
relocations every cycle, no intrinsics, power-loss every byte, unaligned
block_count and lookahead, and odd read_sizes.
There were several configurations were some tests failed because of
limitations in the tests themselves, so many conditions were added
to make sure the configurations can run on as many tests as possible.
- Removed old tests and test scripts
- Reorganize the block devices to live under one directory
- Plugged new test framework into Makefile
renamed:
- scripts/test_.py -> scripts/test.py
- tests_ -> tests
- {file,ram,test}bd/* -> bd/*
It took a surprising amount of effort to make the Makefile behave since
it turns out the "test_%" rule could override "tests/test_%.toml.test"
which is generated as part of test.py.