This is an automated email from the ASF dual-hosted git repository.

jerzy pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mynewt-core.git

commit c63619e8750747d9a3f6c26319cdff6ffeca1ca6
Author: Jerzy Kasenberg <[email protected]>
AuthorDate: Mon Aug 5 15:13:47 2024 +0200

    fs/fcb: Add support for backward walk
    
    This extends fcb_entry structure with field that can change
    direction of the walk.
    
    fcb_getnext() function can now walk in backward direction.
    
    Signed-off-by: Jerzy Kasenberg <[email protected]>
---
 fs/fcb/include/fcb/fcb.h |  65 +++++++
 fs/fcb/src/fcb.c         |   3 +
 fs/fcb/src/fcb_append.c  |   2 +
 fs/fcb/src/fcb_getnext.c | 480 ++++++++++++++++++++++++++++++++++++++++++-----
 fs/fcb/src/fcb_priv.h    |  13 ++
 fs/fcb/src/fcb_walk.c    |  28 +++
 fs/fcb/syscfg.yml        |  28 +++
 7 files changed, 574 insertions(+), 45 deletions(-)

diff --git a/fs/fcb/include/fcb/fcb.h b/fs/fcb/include/fcb/fcb.h
index 4e43e7f0b..d382521fe 100644
--- a/fs/fcb/include/fcb/fcb.h
+++ b/fs/fcb/include/fcb/fcb.h
@@ -38,6 +38,17 @@ extern "C" {
 
 #define FCB_MAX_LEN    (CHAR_MAX | CHAR_MAX << 7) /* Max length of element */
 
+struct fcb_entry_cache {
+    /* Sector number in FCB */
+    uint16_t sector_ix;
+    /* Number of entries that cache can keep */
+    uint16_t cache_size;
+    /* Number of entries in cache */
+    uint16_t cache_count;
+    /* Cached data */
+    uint16_t *cache_data;
+};
+
 /**
  * Entry location is pointer to area (within fcb->f_sectors), and offset
  * within that area.
@@ -47,6 +58,9 @@ struct fcb_entry {
     uint32_t fe_elem_off;      /* start of entry */
     uint32_t fe_data_off;      /* start of data */
     uint16_t fe_data_len;      /* size of data area */
+    uint16_t fe_elem_ix;    /* Element index in current sector */
+    struct fcb_entry_cache *fe_cache;
+    bool fe_step_back; /* walk backwards */
 };
 
 struct fcb {
@@ -55,6 +69,8 @@ struct fcb {
     uint8_t f_version;         /* Current version number of the data */
     uint16_t f_sector_cnt;     /* Number of elements in sector array */
     uint16_t f_scratch_cnt;    /* How many sectors should be kept empty */
+    /** Number of element in active sector (f_active) */
+    uint16_t f_active_sector_entry_count;
     struct flash_area *f_sectors; /* Array of sectors, must be contiguous */
 
     /* Flash circular buffer internal state */
@@ -65,6 +81,41 @@ struct fcb {
     uint8_t f_align;           /* writes to flash have to aligned to this */
 };
 
+/**
+ * Init FCB cache that can be used for walking back optimization.
+ *
+ * Note: Cache can be used to speed up walk back functionality.
+ * Cache uses memory allocated by os_malloc/os_realloc and must
+ * be freed after use with fcb_cache_free().
+ *
+ * To setup cache:
+ * struct fcb_entry_cache cache;
+ * struct fcb_entry loc = {};
+ *
+ * loc.fe_cache = &cache;
+ * fcb_cache_init(fcb, &cache, 100);
+ * loc.fe_step_back = true;
+ *
+ * fcb_getnext(fcb, &loc);
+ *
+ * fcb_cache_free(fcb, cache);
+ *
+ * @param fcb - fcb to init cache for
+ * @param cache - cache to init
+ * @param initial_entry_count - initial number of entries in the cache
+ * @return 0 - on success
+ *         SYS_ENOMEM - when cache memory can't be allocated.
+ */
+int fcb_cache_init(struct fcb *fcb, struct fcb_entry_cache *cache, int 
initial_entry_count);
+
+/**
+ * Free memory allocate by fcb cache.
+ *
+ * @param fcb - fcb associated with cache
+ * @param cache - cache to free
+ */
+void fcb_cache_free(struct fcb *fcb, struct fcb_entry_cache *cache);
+
 /**
  * Error codes.
  */
@@ -102,6 +153,20 @@ typedef int (*fcb_walk_cb)(struct fcb_entry *loc, void 
*arg);
 int fcb_walk(struct fcb *, struct flash_area *, fcb_walk_cb cb, void *cb_arg);
 int fcb_getnext(struct fcb *, struct fcb_entry *loc);
 
+#if MYNEWT_VAL_FCB_BIDIRECTIONAL
+/**
+ * Call 'cb' for every element in flash circular buffer moving
+ * from the newest to oldest entries.
+ *
+ * @param fcb - fcb to walk through
+ * @param cb - function to call for each entry
+ * @param cb_arg - argument to pass to cb
+ * @return 0 after whole buffer was traversed
+ *         non zero value if cb requested termination of the walk
+ */
+int fcb_walk_back(struct fcb *fcb, fcb_walk_cb cb, void *cb_arg);
+#endif
+
 /**
  * Erases the data from oldest sector.
  */
diff --git a/fs/fcb/src/fcb.c b/fs/fcb/src/fcb.c
index 4e18fb867..967f5b460 100644
--- a/fs/fcb/src/fcb.c
+++ b/fs/fcb/src/fcb.c
@@ -82,6 +82,7 @@ fcb_init(struct fcb *fcb)
     fcb->f_active.fe_area = newest_fap;
     fcb->f_active.fe_elem_off = fcb_len_in_flash(fcb, sizeof(struct 
fcb_disk_area));
     fcb->f_active_id = newest;
+    fcb->f_active.fe_elem_ix = 0;
 
     /* Require alignment to be a power of two.  Some code depends on this
      * assumption.
@@ -98,6 +99,8 @@ fcb_init(struct fcb *fcb)
             break;
         }
     }
+    fcb->f_active_sector_entry_count = fcb->f_active.fe_elem_ix;
+
     os_mutex_init(&fcb->f_mtx);
     return rc;
 }
diff --git a/fs/fcb/src/fcb_append.c b/fs/fcb/src/fcb_append.c
index bc44b13ee..b23f43a60 100644
--- a/fs/fcb/src/fcb_append.c
+++ b/fs/fcb/src/fcb_append.c
@@ -101,6 +101,7 @@ fcb_append(struct fcb *fcb, uint16_t len, struct fcb_entry 
*append_loc)
         fcb->f_active.fe_area = fa;
         fcb->f_active.fe_elem_off = fcb_len_in_flash(fcb, sizeof(struct 
fcb_disk_area));
         fcb->f_active_id++;
+        fcb->f_active_sector_entry_count = 0;
     }
 
     rc = flash_area_write(active->fe_area, active->fe_elem_off, tmp_str, cnt);
@@ -131,6 +132,7 @@ fcb_append_finish(struct fcb *fcb, struct fcb_entry *loc)
     uint8_t crc8;
     uint32_t off;
 
+    fcb->f_active_sector_entry_count++;
     rc = fcb_elem_crc8(fcb, loc, &crc8);
     if (rc) {
         return rc;
diff --git a/fs/fcb/src/fcb_getnext.c b/fs/fcb/src/fcb_getnext.c
index 2cf97f696..6c9d17c63 100644
--- a/fs/fcb/src/fcb_getnext.c
+++ b/fs/fcb/src/fcb_getnext.c
@@ -20,6 +20,47 @@
 
 #include "fcb/fcb.h"
 #include "fcb_priv.h"
+#include "defs/error.h"
+
+#if MYNEWT_VAL_FCB_BIDIRECTIONAL
+#define FCB_STEP_BACK(loc) ((loc)->fe_step_back)
+#else
+#define FCB_STEP_BACK(loc) (false)
+#endif
+#if MYNEWT_VAL_FCB_BIDIRECTIONAL && MYNEWT_VAL_FCB_BIDIRECTIONAL_CACHE
+#define FCB_ENTRY_CACHE(loc) ((loc)->fe_cache)
+#define FCB_ENTRY_CACHE_DATA(loc) ((loc)->fe_cache->cache_data)
+#else
+#define FCB_ENTRY_CACHE(loc) (NULL)
+#define FCB_ENTRY_CACHE_DATA(loc) (NULL)
+#endif
+
+/**
+ * Return flash sector size
+ *
+ * @param fcb - fcb to check sector size from
+ * @param sector - 0 base index of sector
+ * @return sector size
+ */
+static int
+fcb_sector_size(struct fcb *fcb, int sector)
+{
+    return (int)fcb->f_sectors[sector].fa_size;
+}
+
+/**
+ * Return offset in flash sector where first entry resides
+ *
+ * This points past FCB header that is present in each sector.
+ *
+ * @param fcb - fcb to test
+ * @return offset of first entry in sector (aligned according to flash)
+ */
+static int
+fcb_start_offset(struct fcb *fcb)
+{
+    return fcb_len_in_flash(fcb, sizeof(struct fcb_disk_area));
+}
 
 int
 fcb_getnext_in_area(struct fcb *fcb, struct fcb_entry *loc)
@@ -30,12 +71,10 @@ fcb_getnext_in_area(struct fcb *fcb, struct fcb_entry *loc)
     if (rc == 0 || rc == FCB_ERR_CRC) {
         do {
             loc->fe_elem_off = loc->fe_data_off +
-              fcb_len_in_flash(fcb, loc->fe_data_len) +
-              fcb_len_in_flash(fcb, FCB_CRC_SZ);
+                               fcb_len_in_flash(fcb, loc->fe_data_len) +
+                               fcb_len_in_flash(fcb, FCB_CRC_SZ);
+            loc->fe_elem_ix++;
             rc = fcb_elem_info(fcb, loc);
-            if (rc != FCB_ERR_CRC) {
-                break;
-            }
         } while (rc == FCB_ERR_CRC);
     }
     return rc;
@@ -51,63 +90,375 @@ fcb_getnext_area(struct fcb *fcb, struct flash_area *fap)
     return fap;
 }
 
+static int
+fcb_get_sector_ix(struct fcb *fcb, struct flash_area *fa)
+{
+    return fa - fcb->f_sectors;
+}
+
+static struct flash_area *
+fcb_get_prev_area(struct fcb *fcb, struct flash_area *fap)
+{
+    fap--;
+    if (fap < fcb->f_sectors) {
+        fap = &fcb->f_sectors[fcb->f_sector_cnt - 1];
+    }
+    return fap;
+}
+
+/**
+ * Return number of bytes needed to encode entry of given length.
+ *
+ * Up to 127 bytes are encoded in single byte.
+ *
+ * @param len
+ * @return 1 when len < 0 otherwise 2
+ */
+static int
+fcb_len_bytes(uint16_t len)
+{
+    return len > 127 ? 2 : 1;
+}
+
 int
-fcb_getnext_nolock(struct fcb *fcb, struct fcb_entry *loc)
+fcb_entry_total_len(struct fcb *fcb, int len)
+{
+    return fcb_len_in_flash(fcb, fcb_len_bytes(len)) +
+           fcb_len_in_flash(fcb, len) +
+           fcb_len_in_flash(fcb, FCB_CRC_SZ);
+}
+
+/**
+ * Compute total bytes that fcb_entry occupies in flash.
+ *
+ * @param fcb - fcb to get information from.
+ * @param loc - entry information as filled with fcb_getnext()
+ * @return number of bytes that entry occupies in flash.
+ */
+static int
+fcb_entry_len_in_flash(struct fcb *fcb, struct fcb_entry *loc)
+{
+    return fcb_entry_total_len(fcb, loc->fe_data_len);
+}
+
+/**
+ * Read length of fcb entry that is located at given flash area offset.
+ *
+ * @param fcb - fcb to read length from
+ * @param fa  - flash area to read
+ * @param offset - offset of entry to read
+ * @return entry length on success
+ *         FCB_ERR_NOVAR - when specified location was not written yet
+ *         FCB_ERR_FLASH - when flash read occurs (flash is corrupted or
+ *                         offset is outside flash area range).
+ */
+static int
+fcb_read_entry_len(struct fcb *fcb, struct flash_area *fa, int offset)
 {
     int rc;
+    uint8_t buf[2];
+    uint16_t len;
 
-    if (loc->fe_area == NULL) {
-        /*
-         * Find the first one we have in flash.
-         */
-        loc->fe_area = fcb->f_oldest;
+    (void)fcb;
+
+    rc = flash_area_read_is_empty(fa, offset, buf, 2);
+
+    if (rc < 0) {
+        return FCB_ERR_FLASH;
+    } else if (rc == 1) {
+        return FCB_ERR_NOVAR;
     }
-    if (loc->fe_elem_off == 0) {
-        /*
-         * If offset is zero, we serve the first entry from the area.
-         */
-        loc->fe_elem_off = fcb_len_in_flash(fcb, sizeof(struct fcb_disk_area));
-        rc = fcb_elem_info(fcb, loc);
+
+    fcb_get_len(buf, &len);
+    return len;
+}
+
+/**
+ * Reads entry length from flash and updates fcb_entry
+ *
+ * fe_data_off and fe_data_len are updated only if record exists in flash.
+ *
+ * @param fcb - fcb to use
+ * @param loc - entry location description to update
+ * @return 0 on success
+ *         FCB_ERR_NOVAR - when specified location was not written yet
+ *         FCB_ERR_FLASH - when flash read occurs (flash is corrupted or
+ *                         offset is outside flash area range).
+ */
+static int
+fcb_update_entry_len(struct fcb *fcb, struct fcb_entry *loc)
+{
+    int rc;
+
+    rc = fcb_read_entry_len(fcb, loc->fe_area, (int)loc->fe_elem_off);
+
+    if (rc >= 0) {
+        loc->fe_data_off = loc->fe_elem_off + fcb_len_in_flash(fcb, 
fcb_len_bytes(rc));
+        loc->fe_data_len = rc;
+        rc = 0;
+    }
+
+    return rc;
+}
+
+/**
+ * Invalidate entry cache
+ *
+ * Called when sector change during walk backward.
+ *
+ * @param fcb - fcb to invalidate cache
+ * @param cache - cache to invalidate
+ */
+static void
+fcb_cache_invalidate(struct fcb *fcb, struct fcb_entry_cache *cache)
+{
+    (void)fcb;
+
+    if (cache) {
+        cache->cache_count = 0;
+        cache->sector_ix = 0xFFFF;
+    }
+}
+
+/**
+ * Function invalidates cache if sector changes
+ *
+ * If sector number is different that one that was cached all entries are
+ * removed.
+ *
+ * @param fcb - fcb to invalidate cache
+ * @param cache - cache to invalidate if needed
+ * @param sector_ix - sector number
+ */
+static void
+fcb_cache_switch_to_sector(struct fcb *fcb, struct fcb_entry_cache *cache, int 
sector_ix)
+{
+    if (cache != NULL && cache->sector_ix != sector_ix) {
+        fcb_cache_invalidate(fcb, cache);
+        cache->sector_ix = sector_ix;
+    }
+}
+
+/**
+ * Retrieve element length from cache
+ *
+ * @param fcb - fcb to use
+ * @param cache - cache to use
+ * @param elem_ix - element index
+ * @return element length if present in cache
+ *         SYS_NOENT if cache does not have element
+ */
+static int
+fcb_cache_elem_len(struct fcb *fcb, struct fcb_entry_cache *cache, int elem_ix)
+{
+    int len;
+
+    (void)fcb;
+
+    if (cache && cache->cache_count > elem_ix) {
+        len = cache->cache_data[elem_ix];
     } else {
-        rc = fcb_getnext_in_area(fcb, loc);
+        len = SYS_ENOENT;
     }
-    switch (rc) {
-    case 0:
-        return 0;
-    case FCB_ERR_CRC:
-        break;
-    default:
-        goto next_sector;
+    return len;
+}
+
+/**
+ * Store entry information in cache.
+ *
+ * Stores entry
+ * @param fcb - fcb to use
+ * @param cache - cache to store information (can be NULL)
+ * @param entry_ix - index of entry in cache
+ * @param offset - offset of entry
+ * @param elem_size - FCB entry size in bytes
+ */
+static void
+fcb_cache_add(struct fcb *fcb, struct fcb_entry_cache *cache, int entry_ix, 
int offset, uint16_t elem_size)
+{
+    /* Total bytes for entries in sector */
+    int sector_data_size;
+    int first_entry_offset;
+
+    if (cache == NULL || cache->cache_data == NULL) {
+        return;
     }
-    while (rc == FCB_ERR_CRC) {
-        rc = fcb_getnext_in_area(fcb, loc);
-        if (rc == 0) {
-            return 0;
+
+    first_entry_offset = fcb_start_offset(fcb);
+
+    /* Data size is size of sector minus sector header */
+    sector_data_size = fcb_sector_size(fcb, cache->sector_ix) - 
first_entry_offset;
+
+    if (entry_ix >= cache->cache_size) {
+        assert(cache->cache_count == cache->cache_size);
+
+        int average_entry_size = (offset - first_entry_offset) / entry_ix;
+        /*  guess cache size based on entries currently in flash */
+        int new_size = cache->cache_size + 5 + (sector_data_size - offset) / 
average_entry_size;
+        size_t cache_byte_count = new_size * sizeof(cache->cache_data[0]);
+
+        cache->cache_data = os_realloc(cache->cache_data, cache_byte_count);
+        if (cache->cache_data == NULL) {
+            cache->sector_ix = 0xFFFF;
+            cache->cache_size = 0;
+            cache->cache_count = 0;
+        } else {
+            cache->cache_size = new_size;
         }
+    }
+
+    if (cache->cache_data) {
+        cache->cache_data[entry_ix] = elem_size;
+        cache->cache_count++;
+    }
+}
 
-        if (rc != FCB_ERR_CRC) {
+int
+fcb_step(struct fcb *fcb, struct fcb_entry *loc, int previous_error)
+{
+    int offset;
+    int entry_len;
+    int rc = 0;
+    int first_entry_offset = fcb_start_offset(fcb);
+
+    if (FCB_STEP_BACK(loc)) {
+        if (loc->fe_area == NULL) {
             /*
-             * Moving to next sector.
+             * Stepping back with empty fcb_entry means that walks starts from
+             * last entry in active sector.
+             * If FCB is not empty f_active has inconsistent data: fe_data_len 
and fe_data_off
+             * refer to last saved entry while fe_elem_off points past this 
entry where new
+             * record should be written hence loc is is reconstructed.
              */
-next_sector:
-            if (loc->fe_area == fcb->f_active.fe_area) {
-                return FCB_ERR_NOVAR;
+            if (fcb->f_active.fe_elem_off != first_entry_offset) {
+                loc->fe_area = fcb->f_active.fe_area;
+                loc->fe_elem_ix = fcb->f_active_sector_entry_count - 1;
+                loc->fe_elem_off =
+                    fcb->f_active.fe_data_off - fcb_len_in_flash(fcb, 
fcb_len_bytes(fcb->f_active.fe_data_len));
+                loc->fe_data_off = fcb->f_active.fe_data_off;
+                loc->fe_data_len = fcb->f_active.fe_data_len;
+            } else {
+                /* FCB is empty */
+                rc = FCB_ERR_NOVAR;
             }
-            loc->fe_area = fcb_getnext_area(fcb, loc->fe_area);
-            loc->fe_elem_off = fcb_len_in_flash(fcb, sizeof(struct 
fcb_disk_area));
-            rc = fcb_elem_info(fcb, loc);
-            switch (rc) {
-            case 0:
-                return 0;
-            case FCB_ERR_CRC:
-                break;
-            default:
-                goto next_sector;
+        } else {
+            struct fcb_entry loc1 = *loc;
+            int target_ix = loc->fe_elem_ix - 1;
+            loc1.fe_data_len = 0;
+
+            /* Begin of sector ? */
+            if (loc->fe_elem_off == first_entry_offset) {
+                /* Oldest sector, nowhere to go */
+                if (fcb->f_oldest == loc->fe_area) {
+                    rc = FCB_ERR_NOVAR;
+                } else {
+                    /* Switch to previous sector and find last entry */
+                    loc1.fe_area = fcb_get_prev_area(fcb, loc->fe_area);
+                    loc1.fe_elem_off = 0;
+                    loc1.fe_elem_ix = 0;
+                }
+            }
+            if (rc != FCB_ERR_NOVAR) {
+                int data_len;
+                struct fcb_entry_cache *cache = FCB_ENTRY_CACHE(loc);
+                /* Empty cache if sector changed */
+                fcb_cache_switch_to_sector(fcb, cache, fcb_get_sector_ix(fcb, 
loc1.fe_area));
+
+                if (loc->fe_elem_off && target_ix >= 0 && (data_len = 
fcb_cache_elem_len(fcb, cache, target_ix)) > 0) {
+                    loc->fe_elem_ix = target_ix;
+                    loc->fe_data_len = data_len;
+                    loc->fe_elem_off = loc->fe_elem_off - 
fcb_entry_total_len(fcb, data_len);
+                    loc->fe_data_off = loc->fe_elem_off + 
fcb_len_in_flash(fcb, data_len);
+                } else {
+                    loc1.fe_elem_off = first_entry_offset;
+                    loc1.fe_data_len = 0;
+                    loc1.fe_elem_ix = 0;
+                    if (cache && cache->cache_data) {
+                        for (loc1.fe_elem_ix = 0;
+                             loc1.fe_elem_ix < cache->cache_count && 
loc1.fe_elem_ix < target_ix;) {
+                            loc1.fe_data_len = fcb_cache_elem_len(fcb, cache, 
loc1.fe_elem_ix);
+                            loc1.fe_elem_off += fcb_entry_total_len(fcb, 
loc1.fe_data_len);
+                            loc1.fe_elem_ix++;
+                        }
+                    }
+
+                    entry_len = 0;
+                    while (true) {
+                        rc = fcb_update_entry_len(fcb, &loc1);
+                        if (rc < 0) {
+                            /*
+                             * Here either there is no more entries or there 
is flash error.
+                             * Get data from loc1 that was set from previous 
entry.
+                             * If entry_len == 0 it means that not even single 
entry was found.
+                             * It means that there was no entry in sector 
return rc without
+                             * modifying loc.
+                             */
+                            if (entry_len) {
+                                /* data_off and data_len are intact from 
previous read */
+                                *loc = loc1;
+                                /* fe_elem_off and fe_elem_ix were already 
moved, correct to last entry */
+                                loc->fe_elem_off -= entry_len;
+                                loc->fe_elem_ix--;
+                                rc = 0;
+                            }
+                            break;
+                        } else if (loc1.fe_elem_ix == target_ix) {
+                            *loc = loc1;
+                            break;
+                        }
+                        fcb_cache_add(fcb, cache, loc1.fe_elem_ix, 
loc1.fe_elem_off, loc1.fe_data_len);
+                        entry_len = fcb_entry_len_in_flash(fcb, &loc1);
+                        offset = (int)loc1.fe_elem_off + entry_len;
+                        loc1.fe_elem_off = offset;
+                        loc1.fe_elem_ix++;
+                    }
+                }
             }
         }
+    } else {
+        if (loc->fe_area == NULL) {
+            loc->fe_area = fcb->f_oldest;
+            loc->fe_elem_off = fcb_start_offset(fcb);
+            loc->fe_elem_ix = 0;
+            loc->fe_data_len = 0;
+        } else if (previous_error == FCB_ERR_NOVAR) {
+            /* If there are more sectors, move to next one */
+            if (loc->fe_area != fcb->f_active.fe_area) {
+                loc->fe_area = fcb_getnext_area(fcb, loc->fe_area);
+                loc->fe_elem_off = fcb_start_offset(fcb);
+                loc->fe_elem_ix = 0;
+                loc->fe_data_len = 0;
+            } else {
+                /* No more sectors NOVAR sticks */
+                rc = FCB_ERR_NOVAR;
+            }
+        } else if (loc->fe_elem_off == 0) {
+            loc->fe_elem_off = fcb_start_offset(fcb);
+            loc->fe_elem_ix = 0;
+            loc->fe_data_len = 0;
+        } else {
+            loc->fe_elem_off += fcb_entry_len_in_flash(fcb, loc);
+            loc->fe_elem_ix++;
+        }
     }
 
-    return 0;
+    return rc;
+}
+
+int
+fcb_getnext_nolock(struct fcb *fcb, struct fcb_entry *loc)
+{
+    int rc = 0;
+
+    do {
+        rc = fcb_step(fcb, loc, rc);
+        if (rc) {
+            break;
+        }
+        rc = fcb_elem_info(fcb, loc);
+    } while (rc == FCB_ERR_CRC || rc == FCB_ERR_NOVAR);
+
+    return rc;
 }
 
 int
@@ -124,3 +475,42 @@ fcb_getnext(struct fcb *fcb, struct fcb_entry *loc)
 
     return rc;
 }
+
+int
+fcb_cache_init(struct fcb *fcb, struct fcb_entry_cache *cache, int 
initial_entry_count)
+{
+    int rc;
+    size_t cache_byte_count;
+
+    (void)fcb;
+
+    cache->sector_ix = 0xFFFF;
+    cache->cache_count = 0;
+
+    cache_byte_count = initial_entry_count * sizeof(cache->cache_data[0]);
+    cache->cache_data = os_malloc(cache_byte_count);
+
+    if (cache->cache_data) {
+        cache->cache_size = initial_entry_count;
+        rc = 0;
+    } else {
+        cache->cache_size = 0;
+        rc = SYS_ENOMEM;
+    }
+
+    return rc;
+}
+
+void
+fcb_cache_free(struct fcb *fcb, struct fcb_entry_cache *cache)
+{
+    (void)fcb;
+
+    if (cache && cache->cache_data) {
+        os_free(cache->cache_data);
+        cache->cache_data = NULL;
+        cache->cache_size = 0;
+        cache->cache_count = 0;
+        cache->sector_ix = 0xFFFF;
+    }
+}
diff --git a/fs/fcb/src/fcb_priv.h b/fs/fcb/src/fcb_priv.h
index d87c0ed34..352aa969a 100644
--- a/fs/fcb/src/fcb_priv.h
+++ b/fs/fcb/src/fcb_priv.h
@@ -58,6 +58,19 @@ int fcb_sector_hdr_init(struct fcb *, struct flash_area 
*fap, uint16_t id);
 int fcb_sector_hdr_read(struct fcb *, struct flash_area *fap,
   struct fcb_disk_area *fdap);
 
+/**
+ * Compute total bytes needed to store entry of given length in flash.
+ *
+ * Returned value includes size of header (just length) size of entry
+ * and size of CRC8. Each of those filed can take more space when flash
+ * alignment is greater then 1.
+ *
+ * @param fcb - fcb with alignment restriction field.
+ * @param len - entry user data length.
+ * @return number of bytes that entry will occupy in flash.
+ */
+int fcb_entry_total_len(struct fcb *fcb, int len);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/fs/fcb/src/fcb_walk.c b/fs/fcb/src/fcb_walk.c
index 17f4be173..b3e61fde6 100644
--- a/fs/fcb/src/fcb_walk.c
+++ b/fs/fcb/src/fcb_walk.c
@@ -51,3 +51,31 @@ fcb_walk(struct fcb *fcb, struct flash_area *fap, 
fcb_walk_cb cb, void *cb_arg)
     os_mutex_release(&fcb->f_mtx);
     return 0;
 }
+
+#if MYNEWT_VAL_FCB_BIDIRECTIONAL
+int
+fcb_walk_back(struct fcb *fcb, fcb_walk_cb cb, void *cb_arg)
+{
+    struct fcb_entry loc = {
+        .fe_step_back = true,
+    };
+    int rc;
+
+    rc = os_mutex_pend(&fcb->f_mtx, OS_WAIT_FOREVER);
+    if (rc && rc != OS_NOT_STARTED) {
+        return FCB_ERR_ARGS;
+    }
+    while ((rc = fcb_getnext_nolock(fcb, &loc)) != FCB_ERR_NOVAR) {
+        os_mutex_release(&fcb->f_mtx);
+        rc = cb(&loc, cb_arg);
+        if (rc) {
+            return rc;
+        }
+        os_mutex_pend(&fcb->f_mtx, OS_WAIT_FOREVER);
+    }
+    os_mutex_release(&fcb->f_mtx);
+
+    return 0;
+}
+
+#endif
diff --git a/fs/fcb/syscfg.yml b/fs/fcb/syscfg.yml
new file mode 100644
index 000000000..9d4374439
--- /dev/null
+++ b/fs/fcb/syscfg.yml
@@ -0,0 +1,28 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#  http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+syscfg.defs:
+    FCB_BIDIRECTIONAL:
+        description: >
+            Enable support for FCB backward walk.
+        value: 0
+    FCB_BIDIRECTIONAL_CACHE:
+        description: >
+            Use heap to cache sector information during walk back.
+        value: 0

Reply via email to