You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by je...@apache.org on 2024/09/11 08:44:17 UTC

(mynewt-core) branch master updated (6a86c2b6b -> f3bd5749b)

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

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


    from 6a86c2b6b tinyusb: Add support for LPC55xx devices
     new 0645b3dee sys/log: Add option to limit number of printed logs
     new c63619e87 fs/fcb: Add support for backward walk
     new 13df1677b fs/fcb: Add fcb_write function
     new 1063e8201 sys/log: Add support for walking back in log
     new ab3f5e376 fs/fcb: Fix fcb_walk local variable initialization
     new f3bd5749b fs/fcb: Clean fcb_append inconsistency

The 6 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 fs/fcb/include/fcb/fcb.h                           |  80 ++++
 fs/fcb/src/fcb.c                                   |   3 +
 fs/fcb/src/fcb_append.c                            |  30 +-
 fs/fcb/src/fcb_getnext.c                           | 480 +++++++++++++++++++--
 fs/fcb/src/fcb_priv.h                              |  13 +
 fs/fcb/src/fcb_walk.c                              |  31 +-
 .../lcd_itf/itf_lcd_da1469x => fs/fcb}/syscfg.yml  |  16 +-
 sys/log/full/include/log/log.h                     |   2 +
 sys/log/full/src/log_fcb.c                         |  35 +-
 sys/log/full/src/log_shell.c                       |  44 +-
 10 files changed, 656 insertions(+), 78 deletions(-)
 copy {hw/drivers/display/lcd_itf/itf_lcd_da1469x => fs/fcb}/syscfg.yml (80%)


(mynewt-core) 05/06: fs/fcb: Fix fcb_walk local variable initialization

Posted by je...@apache.org.
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 ab3f5e376afc0edac49d9bf7d00f9c0a6158b7a3
Author: Jerzy Kasenberg <je...@codecoup.pl>
AuthorDate: Wed Aug 7 13:25:35 2024 +0200

    fs/fcb: Fix fcb_walk local variable initialization
    
    fcb_walk initialize only certain fields of fcb_entry
    before walking.
    This could lead to unpredictable behaviour if additional
    fields were utilized.
    
    Signed-off-by: Jerzy Kasenberg <je...@codecoup.pl>
---
 fs/fcb/src/fcb_walk.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/fs/fcb/src/fcb_walk.c b/fs/fcb/src/fcb_walk.c
index b3e61fde6..9daf55f23 100644
--- a/fs/fcb/src/fcb_walk.c
+++ b/fs/fcb/src/fcb_walk.c
@@ -27,11 +27,10 @@
 int
 fcb_walk(struct fcb *fcb, struct flash_area *fap, fcb_walk_cb cb, void *cb_arg)
 {
-    struct fcb_entry loc;
+    struct fcb_entry loc = {0};
     int rc;
 
     loc.fe_area = fap;
-    loc.fe_elem_off = 0;
 
     rc = os_mutex_pend(&fcb->f_mtx, OS_WAIT_FOREVER);
     if (rc && rc != OS_NOT_STARTED) {


(mynewt-core) 06/06: fs/fcb: Clean fcb_append inconsistency

Posted by je...@apache.org.
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 f3bd5749bb3cbd5af04ad6149709b5d580f41b35
Author: Jerzy Kasenberg <je...@codecoup.pl>
AuthorDate: Wed Aug 7 13:57:17 2024 +0200

    fs/fcb: Clean fcb_append inconsistency
    
    Variable cnt and len were reused leading to some inconsistencies.
    
    1.
    cnt was number of bytes needed to store len but it was later
    changed to store number of bytes in flash to store len (that
    can be larger when flash alignment restrictions are applied).
    Later it was used to actually write len filed to flash, however
    if alignment was not 1 or 2 some random data would be written to
    flash (starting from tmp_str but extending to other values on stack).
    No there is separate variable to keep track how many bytes in
    flash are needed.
    
    2.
    calculating whether entry will fit in current sector,
    fcb_disk_area was not aligned correctly that could lead to
    problems for flash that has alignment that is higher then 8.
    
    3.
    append_loc was partially updated but fe_data_len was never filled
    leaving structure in inconsistent state.
    
    4.
    active->fe_data_len could have incorrect value (rounded up to alignment)
    This was small inconsistency that probably would not result in any
    problem
    
    Signed-off-by: Jerzy Kasenberg <je...@codecoup.pl>
---
 fs/fcb/src/fcb_append.c | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/fs/fcb/src/fcb_append.c b/fs/fcb/src/fcb_append.c
index 444fda25b..ad804c0dc 100644
--- a/fs/fcb/src/fcb_append.c
+++ b/fs/fcb/src/fcb_append.c
@@ -86,24 +86,26 @@ fcb_append(struct fcb *fcb, uint16_t len, struct fcb_entry *append_loc)
     struct flash_area *fa;
     uint8_t tmp_str[2];
     int cnt;
+    int len_bytes_in_flash;
+    int entry_len_in_flash;
     int rc;
 
     cnt = fcb_put_len(tmp_str, len);
     if (cnt < 0) {
         return cnt;
     }
-    cnt = fcb_len_in_flash(fcb, cnt);
-    len = fcb_len_in_flash(fcb, len) + fcb_len_in_flash(fcb, FCB_CRC_SZ);
+    len_bytes_in_flash = fcb_len_in_flash(fcb, cnt);
+    entry_len_in_flash = fcb_entry_total_len(fcb, len);
 
     rc = os_mutex_pend(&fcb->f_mtx, OS_WAIT_FOREVER);
     if (rc && rc != OS_NOT_STARTED) {
         return FCB_ERR_ARGS;
     }
     active = &fcb->f_active;
-    if (active->fe_elem_off + len + cnt > active->fe_area->fa_size) {
+    if (active->fe_elem_off + entry_len_in_flash > active->fe_area->fa_size) {
         fa = fcb_new_area(fcb, fcb->f_scratch_cnt);
         if (!fa || (fa->fa_size <
-            sizeof(struct fcb_disk_area) + len + cnt)) {
+                    fcb_len_in_flash(fcb, sizeof(struct fcb_disk_area)) + entry_len_in_flash)) {
             rc = FCB_ERR_NOSPACE;
             goto err;
         }
@@ -124,9 +126,10 @@ fcb_append(struct fcb *fcb, uint16_t len, struct fcb_entry *append_loc)
     }
     append_loc->fe_area = active->fe_area;
     append_loc->fe_elem_off = active->fe_elem_off;
-    append_loc->fe_data_off = active->fe_elem_off + cnt;
+    append_loc->fe_data_off = active->fe_elem_off + len_bytes_in_flash;
+    append_loc->fe_data_len = len;
 
-    active->fe_elem_off = append_loc->fe_data_off + len;
+    active->fe_elem_off = append_loc->fe_elem_off + entry_len_in_flash;
     active->fe_data_off = append_loc->fe_data_off;
     active->fe_data_len = len;
 


(mynewt-core) 04/06: sys/log: Add support for walking back in log

Posted by je...@apache.org.
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 1063e8201b1b89c6ee19945e9479055957b421be
Author: Jerzy Kasenberg <je...@codecoup.pl>
AuthorDate: Mon Aug 5 15:23:08 2024 +0200

    sys/log: Add support for walking back in log
    
    This adds field to log_offset that allows to specify log
    walk back direction.
    
    For now only log_fcb/fcb use implement walking back.
    
    Signed-off-by: Jerzy Kasenberg <je...@codecoup.pl>
---
 sys/log/full/include/log/log.h |  2 ++
 sys/log/full/src/log_fcb.c     | 35 +++++++++++++++++++++++++----------
 sys/log/full/src/log_shell.c   | 12 +++++++++++-
 3 files changed, 38 insertions(+), 11 deletions(-)

diff --git a/sys/log/full/include/log/log.h b/sys/log/full/include/log/log.h
index 7e874ceb0..d04a191eb 100644
--- a/sys/log/full/include/log/log.h
+++ b/sys/log/full/include/log/log.h
@@ -60,6 +60,8 @@ struct log_offset {
 
     /* Specific to walk / read function. */
     void *lo_arg;
+
+    bool lo_walk_backward;
 };
 
 #if MYNEWT_VAL(LOG_STORAGE_INFO)
diff --git a/sys/log/full/src/log_fcb.c b/sys/log/full/src/log_fcb.c
index 82bd1a415..5ed640820 100644
--- a/sys/log/full/src/log_fcb.c
+++ b/sys/log/full/src/log_fcb.c
@@ -135,6 +135,9 @@ log_fcb_find_gte(struct log *log, struct log_offset *log_offset,
 
     /* Attempt to read the first entry.  If this fails, the FCB is empty. */
     memset(out_entry, 0, sizeof *out_entry);
+    if (log_offset->lo_ts < 0) {
+        out_entry->fe_step_back = true;
+    }
     rc = fcb_getnext(fcb, out_entry);
     if (rc == FCB_ERR_NOVAR) {
         return SYS_ENOENT;
@@ -146,7 +149,7 @@ log_fcb_find_gte(struct log *log, struct log_offset *log_offset,
      * if timestamp for request is < 0, return last log entry
      */
     if (log_offset->lo_ts < 0) {
-        *out_entry = fcb->f_active;
+        out_entry->fe_step_back = false;
         return 0;
     }
 
@@ -348,7 +351,7 @@ log_fcb_append_body(struct log *log, const struct log_entry_hdr *hdr,
     }
     memcpy(buf + hdr_len, u8p, hdr_alignment);
 
-    rc = flash_area_write(loc.fe_area, loc.fe_data_off, buf, chunk_sz);
+    rc = fcb_write(fcb, &loc, buf, chunk_sz);
     if (rc != 0) {
         return rc;
     }
@@ -359,8 +362,7 @@ log_fcb_append_body(struct log *log, const struct log_entry_hdr *hdr,
     body_len -= hdr_alignment;
 
     if (body_len > 0) {
-        rc = flash_area_write(loc.fe_area, loc.fe_data_off + chunk_sz, u8p,
-                              body_len);
+        rc = fcb_write(fcb, &loc, u8p, body_len);
         if (rc != 0) {
             return rc;
         }
@@ -578,6 +580,7 @@ log_fcb_walk_impl(struct log *log, log_walk_func_t walk_func,
     struct fcb_entry loc;
     struct flash_area *fap;
     int rc;
+    struct fcb_entry_cache cache;
 
     fcb_log = log->l_arg;
     fcb = &fcb_log->fl_fcb;
@@ -596,6 +599,14 @@ log_fcb_walk_impl(struct log *log, log_walk_func_t walk_func,
     }
     fap = loc.fe_area;
 
+    if (log_offset->lo_walk_backward) {
+        loc.fe_step_back = true;
+        if (MYNEWT_VAL(FCB_BIDIRECTIONAL_CACHE)) {
+            fcb_cache_init(fcb, &cache, 50);
+            loc.fe_cache = &cache;
+        }
+    }
+
 #if MYNEWT_VAL(LOG_FCB_BOOKMARKS)
     /* If a minimum index was specified (i.e., we are not just retrieving the
      * last entry), add a bookmark pointing to this walk's start location.
@@ -605,24 +616,28 @@ log_fcb_walk_impl(struct log *log, log_walk_func_t walk_func,
     }
 #endif
 
+    rc = 0;
     do {
         if (area) {
             if (fap != loc.fe_area) {
-                return 0;
+                break;
             }
         }
 
         rc = walk_func(log, log_offset, &loc, loc.fe_data_len);
         if (rc != 0) {
-            if (rc < 0) {
-                return rc;
-            } else {
-                return 0;
+            if (rc > 0) {
+                rc = 0;
             }
+            break;
         }
     } while (fcb_getnext(fcb, &loc) == 0);
 
-    return 0;
+    if (log_offset->lo_walk_backward && MYNEWT_VAL(FCB_BIDIRECTIONAL_CACHE)) {
+        fcb_cache_free(fcb, &cache);
+    }
+
+    return rc;
 }
 
 static int
diff --git a/sys/log/full/src/log_shell.c b/sys/log/full/src/log_shell.c
index e252d33f8..342fb202b 100644
--- a/sys/log/full/src/log_shell.c
+++ b/sys/log/full/src/log_shell.c
@@ -156,6 +156,7 @@ shell_log_dump_cmd(int argc, char **argv)
     bool stream;
     bool partial_match = false;
     bool clear_log;
+    bool reverse = false;
     bool dump_logs = true;
     struct walk_arg arg = {};
     int i;
@@ -193,6 +194,10 @@ shell_log_dump_cmd(int argc, char **argv)
             dump_logs = false;
             continue;
         }
+        if (0 == strcmp(argv[i], "-r")) {
+            reverse = true;
+            continue;
+        }
 
         /* the -c option is to clear a log (or logs). */
         if (!strcmp(argv[i], "-c")) {
@@ -243,7 +248,12 @@ shell_log_dump_cmd(int argc, char **argv)
                 console_printf("Dumping log %s\n", log->l_name);
             }
 
-            log_offset.lo_ts = 0;
+            if (reverse) {
+                log_offset.lo_ts = -1;
+                log_offset.lo_walk_backward = true;
+            } else {
+                log_offset.lo_ts = 0;
+            }
             log_last_index = log_get_last_index(log);
             if (log_limit == 0 || log_last_index < log_limit) {
                 log_offset.lo_index = 0;


(mynewt-core) 01/06: sys/log: Add option to limit number of printed logs

Posted by je...@apache.org.
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 0645b3deec84efe146bf0c1227f7ec4cc7f5da53
Author: Jerzy Kasenberg <je...@codecoup.pl>
AuthorDate: Mon Aug 5 14:36:12 2024 +0200

    sys/log: Add option to limit number of printed logs
    
    Option -s allows to skip number of logs from the start
    
    Usage:
    log [-n <number>] [-s <skip_number> ]
    
    i.e.:
    log -s 100 -n 10
    
    will print at most 10 entries from each log skipping first 100 entries
    
    Signed-off-by: Jerzy Kasenberg <je...@codecoup.pl>
---
 sys/log/full/src/log_shell.c | 32 +++++++++++++++++++++++++++-----
 1 file changed, 27 insertions(+), 5 deletions(-)

diff --git a/sys/log/full/src/log_shell.c b/sys/log/full/src/log_shell.c
index 0de60d776..e252d33f8 100644
--- a/sys/log/full/src/log_shell.c
+++ b/sys/log/full/src/log_shell.c
@@ -43,7 +43,11 @@ static uint32_t shell_log_count;
 
 
 struct walk_arg {
+    /* Number of entries to skip */
+    uint32_t skip;
+    /* Number of entries to process */
     uint32_t count_limit;
+    /* Entry number */
     uint32_t count;
 };
 
@@ -56,7 +60,7 @@ shell_log_count_entry(struct log *log, struct log_offset *log_offset,
     shell_log_count++;
     if (arg) {
         arg->count++;
-        if (arg->count >= arg->count_limit) {
+        if ((arg->count_limit > 0) && (arg->count >= arg->count_limit)) {
             return 1;
         }
     }
@@ -81,6 +85,14 @@ shell_log_dump_entry(struct log *log, struct log_offset *log_offset,
     bool read_data = ueh->ue_etype != LOG_ETYPE_CBOR;
     bool read_hash = ueh->ue_flags & LOG_FLAGS_IMG_HASH;
 
+    if (arg) {
+        arg->count++;
+        /* Continue walk if number of entries to skip not reached yet */
+        if (arg->count <= arg->skip) {
+            return 0;
+        }
+    }
+
     dlen = min(len, 128);
 
     if (read_data) {
@@ -125,8 +137,7 @@ shell_log_dump_entry(struct log *log, struct log_offset *log_offset,
 
     console_write("\n", 1);
     if (arg) {
-        arg->count++;
-        if (arg->count >= arg->count_limit) {
+        if ((arg->count_limit > 0) && (arg->count - arg->skip >= arg->count_limit)) {
             return 1;
         }
     }
@@ -137,7 +148,7 @@ int
 shell_log_dump_cmd(int argc, char **argv)
 {
     struct log *log;
-    struct log_offset log_offset;
+    struct log_offset log_offset = {};
     bool list_only = false;
     char *log_name = NULL;
     uint32_t log_last_index = 0;
@@ -167,6 +178,17 @@ shell_log_dump_cmd(int argc, char **argv)
             ++i;
             continue;
         }
+        if (0 == strcmp(argv[i], "-s")) {
+            if (i + 1 < argc) {
+                arg.skip = parse_ll_bounds(argv[i + 1], 0, 1000000, &rc);
+                if (rc) {
+                    arg.skip = 0;
+                }
+                log_offset.lo_arg = &arg;
+            }
+            ++i;
+            continue;
+        }
         if (0 == strcmp(argv[i], "-t")) {
             dump_logs = false;
             continue;
@@ -221,7 +243,6 @@ shell_log_dump_cmd(int argc, char **argv)
                 console_printf("Dumping log %s\n", log->l_name);
             }
 
-            log_offset.lo_arg = NULL;
             log_offset.lo_ts = 0;
             log_last_index = log_get_last_index(log);
             if (log_limit == 0 || log_last_index < log_limit) {
@@ -232,6 +253,7 @@ shell_log_dump_cmd(int argc, char **argv)
             log_offset.lo_data_len = 0;
 
             if (dump_logs) {
+                arg.count = 0;
                 rc = log_walk_body(log, shell_log_dump_entry, &log_offset);
             } else {
                 /* Measure time for log_walk */


(mynewt-core) 02/06: fs/fcb: Add support for backward walk

Posted by je...@apache.org.
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 <je...@codecoup.pl>
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 <je...@codecoup.pl>
---
 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


(mynewt-core) 03/06: fs/fcb: Add fcb_write function

Posted by je...@apache.org.
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 13df1677b04c2781724877820fb2ede8002503a0
Author: Jerzy Kasenberg <je...@codecoup.pl>
AuthorDate: Wed Aug 7 13:39:57 2024 +0200

    fs/fcb: Add fcb_write function
    
    Previously fcb sequence to write FCB entry was:
    fcb_append(fcb, len, loc)
    flash_area_write(fa,...)
    fcb_finish(fcb, loc)
    
    This adds function fcb_write() that should be used instead
    of direct call to flash_area_write to make it more consistent.
    
    Signed-off-by: Jerzy Kasenberg <je...@codecoup.pl>
---
 fs/fcb/include/fcb/fcb.h | 15 +++++++++++++++
 fs/fcb/src/fcb_append.c  | 13 +++++++++++++
 2 files changed, 28 insertions(+)

diff --git a/fs/fcb/include/fcb/fcb.h b/fs/fcb/include/fcb/fcb.h
index d382521fe..7f97b9ee1 100644
--- a/fs/fcb/include/fcb/fcb.h
+++ b/fs/fcb/include/fcb/fcb.h
@@ -141,6 +141,21 @@ int fcb_init(struct fcb *fcb);
 int fcb_append(struct fcb *, uint16_t len, struct fcb_entry *loc);
 int fcb_append_finish(struct fcb *, struct fcb_entry *append_loc);
 
+/**
+ * Write to flash user data.
+ *
+ * Function should be called after fcb_append is called and before fcb_finish
+ * This is wrapper for flash_area_write() function and uses loc for starting
+ * location. loc is modified and can be used for subsequent writes.
+ *
+ * @param fcb - fcb to write entry to
+ * @param loc - location of the entry
+ * @param buf - data to write
+ * @param len - number of bytes to write to fcb
+ * @return 0 on success, non-zero on failure
+ */
+int fcb_write(struct fcb *fcb, struct fcb_entry *loc, const uint8_t *buf, size_t len);
+
 /**
  * Walk over all entries in FCB.
  * cb gets called for every entry. If cb wants to stop the walk, it should
diff --git a/fs/fcb/src/fcb_append.c b/fs/fcb/src/fcb_append.c
index b23f43a60..444fda25b 100644
--- a/fs/fcb/src/fcb_append.c
+++ b/fs/fcb/src/fcb_append.c
@@ -66,6 +66,19 @@ fcb_append_to_scratch(struct fcb *fcb)
     return FCB_OK;
 }
 
+int
+fcb_write(struct fcb *fcb, struct fcb_entry *loc, const uint8_t *buf, size_t len)
+{
+    int rc;
+
+    rc = flash_area_write(loc->fe_area, loc->fe_data_off, buf, len);
+    if (rc == 0) {
+        loc->fe_data_off += len;
+    }
+
+    return rc;
+}
+
 int
 fcb_append(struct fcb *fcb, uint16_t len, struct fcb_entry *append_loc)
 {