Commit Graph

18 Commits

Author SHA1 Message Date
Christopher Haster 6f1d110e01 Changed leb128 related functions to operate on uint32_t
So unsigned instead of signed. The original intention of using int32_t
was to hint that the sign bit should be reserved, but this may just
confuse if our leb128 encoding has a sign representation, which it does
not.
2024-02-09 14:35:23 -06:00
Christopher Haster e8b8c010e6 Dropped uncrc32c, use flcksum for aligning checksums
While definitely winning cool points, uncrc32cs have a number of
problems:

1. uncrc32c is relatively unflexible, being limited to only CRC-related
   checksums, and probably violating some properties of cryptographic
   hashes if possible there.

2. Code savings are minimal, a reversed crc32c implementation is a only
   a little less costly than the logic to save aligned CRCs, and since
   it's not on the hot-path, the stack cost is ~zero.

3. uncrc32c may come with a high computation cost.

   We aren't measuring this, but uncrc32c either operates at the
   bit-level, or requires a second set of tables which is unreasonable
   for littlefs's use case.

   With uncrc32c you need to update the checksum based on every bit in
   the extra unaligned data, up to prog_size. With flcksums it's just a
   copy of a word, and prog_size has no impact.

So for now dropping uncrc32c, though this can always be reverted in the
future.
2023-12-17 15:18:26 -06:00
Christopher Haster 006d656da2 Fixed unaligned data checksumming in two ways (uncrc32c, flcksum)
Checksumming unaligned data during block compaction is surprisingly
tricky. We don't know if our data will be aligned until after
a potentially unbounded number lookups, we need to write data into our
pcache as we go to avoid unnecessary lookups, but if we end up unaligned
we need to revert our checksum to the checksum of the aligned data.

The way I see it there are 4 options:

1. Calculate the checksum after writing data into the block.

   This is the most expensive option, requiring a full second read of
   the data to calculate the checksum. It is simple though.

2. Do a pass over the btree to figure out alignment before writing.

   This at least only reads metadata twice, so is more efficient than
   the 1st option.

3. Keep track of the aligned checksum on each flush, falling back to the
   last flushed checksum if we need to correct alignment.

   This solution is flexible though requires some extra state to track
   multiple checksums.

4. Leverage the math behind CRCs to run the CRC backwards when we
   truncate for alignment.

   This works, though a bit inefficiently, but is strictly tied to
   CRC-related checksums.

   By inefficient I mean that we would likely be limited to a bit-level
   "uncrc32c". It's possible to create nibble/byte tables for uncrc32c,
   but this adds significant code cost for a relatively uncritical
   function.

   I was hopeful that we could leverage the existing tables in both
   functions, but unfortunately it doesn't work out like that. You could
   scan the crc32c table to find the constant to reverse, but this
   requires ~16*2 or ~256 operations vs "naive" ~8 operations per byte.

This commit implements both 3 and 4, defaulting to 4 unless
LFS_NO_UNCRC32C is defined.

The current lfs_uncrc32c implementation is a simple bit-level
implementation, but does allow for crc32c truncation without any extra
state.

              code          stack
  before:    32044           2880
  uncrc32c:  32108 (+0.2%)   2880 (+0.0%)
  flcksum:   32132 (+0.3%)   2880 (+0.0%)
2023-12-17 15:18:10 -06:00
Christopher Haster d8f988a8fc Made data read functions "consume" their data pointers
Composable parsing functions always feel a bit weird to me in C. I don't
know if this is because of something C lacks, such as multiple return
values, or if composable parsers are just inherently awkward to describe
in procedural languages because of the different levels of state.

But I think the API here is pretty ok. The main idea is that data
parsers can be added as functions in the lfsr_data_* namespace that take
lfsr_data_t as a mutable reference, updating the lfsr_data_t's internal
state as data is parsed.

In practice you only need a couple of primitives, bytes, le32s, leb128s,
that touch the internals of lfsr_data_t, and the other parsers can be
built using these.

This leverages the pointer-like abstraction of lfsr_data_t, and avoids
needing to keep track of offsets. And thanks to lfsr_data_t being
relatively cheap to make copies, this API is relatively flexible.

Some other tweaks:

- Signed leb128 overflow detection is moved up into lfs_fromleb128.
  littlefs now assumes _all_ leb128s are 31-bits, which is useful for
  leveraging the sign bit internally.

  This also fixes the an issue in overflow detection in lfs_fromleb128
  which wouldn't catch overflows in the last byte of a >32-bit leb128.

- Most lfsr_data_t functions now take a pointer. This offered a small
  bit of code savings and feels more natural in C. Though most functions
  that accept lfsr_data_t still take a copy. Most of these functions
  would need to make a copy anyways now that the parsers are consuming,
  and these copies avoid concerns about shared state.

  At 3-words, lfsr_data_t is right at that boundary of questionable
  reasonableness for copying, but copying is a very useful feature of
  this struct.

This ends up with some decent code/stack savings:

            code          stack
  before:  22118           2048
  after:   21722 (-1.8%)   1992 (-2.7%)
2023-08-10 11:13:14 -05:00
Christopher Haster 355c7466f1 Added better protection against internal leb128 underflow
There have already been a number of bugs that end up writing -1 out as
leb128s. The current encoder doesn't know the different betwee -1 and
0xffffffff, so asserting before this situation can happen is quite
important for preventing these bad leb128s from ever making it into a
stable version.

Also dropped LFS_ERR_OVERFLOW to use LFS_ERR_CORRUPT for bad leb128
encodings. These end up meaning the same thing to higher layers anyways.
2023-04-14 00:29:36 -05:00
Christopher Haster 2802880eaa Initial groundwork for rbyd trees
- primitive lfs_rbyd_fetch
- primitive lfs_rbyd_commit
- tag reading/progging and encoding machinery

The tag encoding scheme here uses pairs of leb128s, encoding either
a normal tag:

  iiii iiiiiii iiiiiTT TTTTTTt ttttt0v
                   ^--------^------^-^- 16-bit id
                            '------|-|- 8-bit type2
                                   '-|- 6-bit type1
                                     '- valid bit
  llll lllllll lllllll lllllll lllllll
                                     ^- n-bit length

Or an alt pointer:

  wwww wwwwwww wwwwwww wwwwwww wwwcd1v
                                 ^^^-^- 28-bit weight
                                  '|-|- color bit
                                   '-|- direction bit
                                     '- valid bit
  jjjj jjjjjjj jjjjjjj jjjjjjj jjjjjjj
                                     ^- n-bit jump

Note that two bits overlap the alt pointer dir/color encoding, this
is actually not a problem at all since some tags (crcs/fcrcs) don't
participate in the rbyd tree and can use these bits.

There's a number of benefits to using leb128s, which should probably
be written about, most notably is the abstraction of the device's
word-size. The "n-bits" above can be whatever word size works on the
device, trading off code-size for storage capabilities without breaking
compatibility with other devices. This will eventually be negotiated via
the superblock.
2023-02-12 12:37:59 -06:00
Christopher Haster 0f72d18b14 Added lfs_crc32c
crc32c, with a polynomial of 0x11edc6f41, is generally numerically superior
to the more common crc32 standard. Catching more bit errors across
a wider range of message messages (except 2-bit errors) without any changes
to the underlying algorithm.

Philip Koopman has a large body of work exploring optimal polynomials here:
http://users.ece.cmu.edu/~koopman/crc/crc32.html

And from his experiments we know the maximum message size where we can
still detect a given number of bit errors for each polynomial:

                       1          2          3    4    5   6   7  8  9 10 11 12 13 14 15 16 17
  crc32  0x104c11db7 = ∞ 4294967263      91607 2974  268 171  91 57 34 21 12 10 10 10  -  -  -
  crc32c 0x11edc6f41 = ∞ 2147483615 2147483615 5243 5243 177 177 47 47 20 20  8  8  6  6  1  1

So really crc32c should be prefered where possible. Koopman also has
alternative polynomials with slightly different properties, but crc32c
is already popular enough to have a decent amount of hardware support.

---

Another nice feature of crc32c is that its polynomial has even parity.
It turns out that even-parity polynomials give us the nifty property
parity(crc(m)) == parity(m).

A quick proof:

  crc(m) = m(x) x^|P|-1 mod P(x)

  parity(m) = m(x) x mod x+1

  though note: x mod x+1 = 1, by hand
  so:

  parity(m) = m(x)*1 mod x+1
            = m(x) mod x+1

  solving for parity(crc(m)):

  parity(crc(m)) = (m(x) x^|P|-1 mod P(x)) mod x+1

  note: (a mod b) mod c = a mod c, if c divides b,
    aka (a mod b) mod c = a mod c, if b mod c = 0

  so if P(x) mod x+1 = 0,
    aka if parity(P) = 0:

  parity(crc(m)) = m(x) x^|P|-1 mod x+1

  but, like before: x^|P|-1 mod x+1 = 1, by hand
  so:

  parity(crc(m)) = m(x)*1 mod x+1
                 = m(x) mod x+1
                 = parity(m)

  so if parity(P) = 0:

  parity(crc(m)) = parity(m)

This has the potential to replace the 1-bit counter in the metadata tags
with a more general solution that doesn't require extra state.
2022-12-18 11:07:41 -06:00
Christopher Haster 2db5dc80c2 Update copyright notice 2022-03-20 23:03:52 -05:00
Christopher Haster 10f45ac02f Changed lfs_crc to match more common API
In looking at the common CRC APIs out there, this seemed the most
common. At least more common than the current modified-in-place pointer
API. It also seems to have a slightly better code footprint. I'm blaming
pointer optimization issues.

One downside is that lfs_crc can't report errors, however it was already
assumed that lfs_crc can not error.
2018-10-16 20:53:19 -05:00
Christopher Haster 6beff502e9 Changed license to BSD-3-Clause
For better compatibility with GPL v2

With permissions from:
- aldot
- Sim4n6
- jrast
2018-06-21 11:41:43 -05:00
Christopher Haster 1476181bd1 Added LFS_CONFIG for user provided configuration of the utils
Suggested by sn00pster, LFS_CONFIG is an opt-in user provided
configuration file that will override the util implementation in
lfs_util.h. This is useful for allowing system-specific overrides
without needing to rely on git merges or other forms of patching
for updates.
2018-02-22 13:39:24 -06:00
Christopher Haster 83d4c614a0 Updated copyright
Due to employee contract
Per ARM license remains under Apache 2.0
2017-10-12 20:29:10 -05:00
Christopher Haster 663e953a50 Adopted the Apache 2.0 license 2017-07-08 11:49:40 -05:00
Christopher Haster fe28ea0f93 Added internal check of data written to disk
Before, the littlefs relied on the underlying block device
to report corruption that occurs when writing data to disk.
This requirement is easy to miss or implement incorrectly, since
the error detection is only required when a block becomes corrupted,
which is very unlikely to happen until late in the block device's
lifetime.

The littlefs can detect corruption itself by reading back written data.
This requires a bit of care to reuse the available buffers, and may rely
on checksums to avoid additional RAM requirements.

This does have a runtime penalty with the extra read operations, but
should make the littlefs much more robust to different implementations.
2017-06-28 15:50:47 -05:00
Christopher Haster b55719bab1 Adopted more conventional buffer parameter ordering
Adopted buffer followed by size. The other order was original
chosen due to some other functions with a more complicated
parameter list.

This convention is important, as the bd api is one of the main
apis facing porting efforts.
2017-04-23 23:58:43 -05:00
Christopher Haster 5790ec2ce4 Structured some of the bulk of the codebase
- Removed lfs_config.h, distributed between lfs.h and lfs_util.h
- Moved some functions that felt out of place
2017-04-23 21:40:03 -05:00
Christopher Haster 3b9d6630c8 Restructured directory code
After quite a bit of prototyping, settled on the following functions:
- lfs_dir_alloc  - create a new dir
- lfs_dir_fetch  - load and check a dir pair from disk
- lfs_dir_commit - save a dir pair to disk
- lfs_dir_shift  - shrink a dir pair to disk
- lfs_dir_append - add a dir entry, creating dirs if needed
- lfs_dir_remove - remove a dir entry, dropping dirs if needed

Additionally, followed through with a few other tweaks
2017-04-18 01:44:01 -05:00
Christopher Haster 84a57642e5 Restructured the major interfaces of the filesystem 2017-03-25 19:23:26 -05:00