Replace flash_area_erase with flash_area_flatten to allow FCB Settings backend to work on devices that do not provide erase callback. Signed-off-by: Dominik Ermel <dominik.ermel@nordicsemi.no>
456 lines
10 KiB
C
456 lines
10 KiB
C
/*
|
|
* Copyright (c) 2018 Nordic Semiconductor ASA
|
|
* Copyright (c) 2015 Runtime Inc
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <stdbool.h>
|
|
#include <zephyr/fs/fcb.h>
|
|
#include <string.h>
|
|
|
|
#include <zephyr/settings/settings.h>
|
|
#include "settings/settings_fcb.h"
|
|
#include "settings_priv.h"
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_DECLARE(settings, CONFIG_SETTINGS_LOG_LEVEL);
|
|
|
|
#if DT_HAS_CHOSEN(zephyr_settings_partition)
|
|
#define SETTINGS_PARTITION DT_FIXED_PARTITION_ID(DT_CHOSEN(zephyr_settings_partition))
|
|
#else
|
|
#define SETTINGS_PARTITION FIXED_PARTITION_ID(storage_partition)
|
|
#endif
|
|
|
|
#define SETTINGS_FCB_VERS 1
|
|
|
|
int settings_backend_init(void);
|
|
|
|
static int settings_fcb_load(struct settings_store *cs,
|
|
const struct settings_load_arg *arg);
|
|
static int settings_fcb_save(struct settings_store *cs, const char *name,
|
|
const char *value, size_t val_len);
|
|
static void *settings_fcb_storage_get(struct settings_store *cs);
|
|
|
|
static const struct settings_store_itf settings_fcb_itf = {
|
|
.csi_load = settings_fcb_load,
|
|
.csi_save = settings_fcb_save,
|
|
.csi_storage_get = settings_fcb_storage_get
|
|
};
|
|
|
|
/**
|
|
* @brief Get the flash area id of the storage partition
|
|
*
|
|
* The implementation of this function provided is weak to let user defines its own function.
|
|
* This may prove useful for devices using bank swap, in that case the flash area id changes based
|
|
* on the bank swap state.
|
|
* See #47732
|
|
*
|
|
* @return Flash area id
|
|
*/
|
|
__weak int settings_fcb_get_flash_area(void)
|
|
{
|
|
return SETTINGS_PARTITION;
|
|
}
|
|
|
|
int settings_fcb_src(struct settings_fcb *cf)
|
|
{
|
|
int rc;
|
|
|
|
cf->cf_fcb.f_version = SETTINGS_FCB_VERS;
|
|
cf->cf_fcb.f_scratch_cnt = 1;
|
|
|
|
while (1) {
|
|
rc = fcb_init(settings_fcb_get_flash_area(), &cf->cf_fcb);
|
|
if (rc) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* Check if system was reset in middle of emptying a sector.
|
|
* This situation is recognized by checking if the scratch block
|
|
* is missing.
|
|
*/
|
|
if (fcb_free_sector_cnt(&cf->cf_fcb) < 1) {
|
|
|
|
rc = flash_area_flatten(cf->cf_fcb.fap,
|
|
cf->cf_fcb.f_active.fe_sector->fs_off,
|
|
cf->cf_fcb.f_active.fe_sector->fs_size);
|
|
|
|
if (rc) {
|
|
return -EIO;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
cf->cf_store.cs_itf = &settings_fcb_itf;
|
|
settings_src_register(&cf->cf_store);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int settings_fcb_dst(struct settings_fcb *cf)
|
|
{
|
|
cf->cf_store.cs_itf = &settings_fcb_itf;
|
|
settings_dst_register(&cf->cf_store);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Check if there is any duplicate of the current setting
|
|
*
|
|
* This function checks if there is any duplicated data further in the buffer.
|
|
*
|
|
* @param cf FCB handler
|
|
* @param entry_ctx Current entry context
|
|
* @param name The name of the current entry
|
|
*
|
|
* @retval false No duplicates found
|
|
* @retval true Duplicate found
|
|
*/
|
|
static bool settings_fcb_check_duplicate(struct settings_fcb *cf,
|
|
const struct fcb_entry_ctx *entry_ctx,
|
|
const char * const name)
|
|
{
|
|
struct fcb_entry_ctx entry2_ctx = *entry_ctx;
|
|
|
|
while (fcb_getnext(&cf->cf_fcb, &entry2_ctx.loc) == 0) {
|
|
char name2[SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN + 1];
|
|
size_t name2_len;
|
|
|
|
if (settings_line_name_read(name2, sizeof(name2), &name2_len,
|
|
&entry2_ctx)) {
|
|
LOG_ERR("failed to load line");
|
|
continue;
|
|
}
|
|
name2[name2_len] = '\0';
|
|
if (!strcmp(name, name2)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static int read_entry_len(const struct fcb_entry_ctx *entry_ctx, off_t off)
|
|
{
|
|
if (off >= entry_ctx->loc.fe_data_len) {
|
|
return 0;
|
|
}
|
|
return entry_ctx->loc.fe_data_len - off;
|
|
}
|
|
|
|
static int settings_fcb_load_priv(struct settings_store *cs,
|
|
line_load_cb cb,
|
|
void *cb_arg,
|
|
bool filter_duplicates)
|
|
{
|
|
struct settings_fcb *cf = CONTAINER_OF(cs, struct settings_fcb, cf_store);
|
|
struct fcb_entry_ctx entry_ctx = {
|
|
{.fe_sector = NULL, .fe_elem_off = 0},
|
|
.fap = cf->cf_fcb.fap
|
|
};
|
|
int rc;
|
|
|
|
while ((rc = fcb_getnext(&cf->cf_fcb, &entry_ctx.loc)) == 0) {
|
|
char name[SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN + 1];
|
|
size_t name_len;
|
|
int rc2;
|
|
bool pass_entry = true;
|
|
|
|
rc2 = settings_line_name_read(name, sizeof(name), &name_len,
|
|
(void *)&entry_ctx);
|
|
if (rc2) {
|
|
LOG_ERR("Failed to load line name: %d", rc2);
|
|
continue;
|
|
}
|
|
name[name_len] = '\0';
|
|
|
|
if (filter_duplicates &&
|
|
(!read_entry_len(&entry_ctx, name_len+1) ||
|
|
settings_fcb_check_duplicate(cf, &entry_ctx, name))) {
|
|
pass_entry = false;
|
|
}
|
|
/*name, val-read_cb-ctx, val-off*/
|
|
/* take into account '=' separator after the name */
|
|
if (pass_entry) {
|
|
cb(name, &entry_ctx, name_len + 1, cb_arg);
|
|
}
|
|
}
|
|
if (rc == -ENOTSUP) {
|
|
rc = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int settings_fcb_load(struct settings_store *cs,
|
|
const struct settings_load_arg *arg)
|
|
{
|
|
return settings_fcb_load_priv(
|
|
cs,
|
|
settings_line_load_cb,
|
|
(void *)arg,
|
|
true);
|
|
}
|
|
|
|
static int read_handler(void *ctx, off_t off, char *buf, size_t *len)
|
|
{
|
|
struct fcb_entry_ctx *entry_ctx = ctx;
|
|
|
|
if (off >= entry_ctx->loc.fe_data_len) {
|
|
*len = 0;
|
|
return 0;
|
|
}
|
|
|
|
if ((off + *len) > entry_ctx->loc.fe_data_len) {
|
|
*len = entry_ctx->loc.fe_data_len - off;
|
|
}
|
|
|
|
return flash_area_read(entry_ctx->fap,
|
|
FCB_ENTRY_FA_DATA_OFF(entry_ctx->loc) + off, buf,
|
|
*len);
|
|
}
|
|
|
|
static void settings_fcb_compress(struct settings_fcb *cf)
|
|
{
|
|
int rc;
|
|
struct fcb_entry_ctx loc1;
|
|
struct fcb_entry_ctx loc2;
|
|
char name1[SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN];
|
|
char name2[SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN];
|
|
int copy;
|
|
uint8_t rbs;
|
|
|
|
rc = fcb_append_to_scratch(&cf->cf_fcb);
|
|
if (rc) {
|
|
return; /* XXX */
|
|
}
|
|
|
|
rbs = flash_area_align(cf->cf_fcb.fap);
|
|
|
|
loc1.fap = cf->cf_fcb.fap;
|
|
|
|
loc1.loc.fe_sector = NULL;
|
|
loc1.loc.fe_elem_off = 0U;
|
|
|
|
while (fcb_getnext(&cf->cf_fcb, &loc1.loc) == 0) {
|
|
if (loc1.loc.fe_sector != cf->cf_fcb.f_oldest) {
|
|
break;
|
|
}
|
|
|
|
size_t val1_off;
|
|
|
|
rc = settings_line_name_read(name1, sizeof(name1), &val1_off,
|
|
&loc1);
|
|
if (rc) {
|
|
continue;
|
|
}
|
|
|
|
if (val1_off + 1 == loc1.loc.fe_data_len) {
|
|
/* Lack of a value so the record is a deletion-record */
|
|
/* No sense to copy empty entry from */
|
|
/* the oldest sector */
|
|
continue;
|
|
}
|
|
|
|
loc2 = loc1;
|
|
copy = 1;
|
|
|
|
while (fcb_getnext(&cf->cf_fcb, &loc2.loc) == 0) {
|
|
size_t val2_off;
|
|
|
|
rc = settings_line_name_read(name2, sizeof(name2),
|
|
&val2_off, &loc2);
|
|
if (rc) {
|
|
continue;
|
|
}
|
|
|
|
if ((val1_off == val2_off) &&
|
|
!memcmp(name1, name2, val1_off)) {
|
|
copy = 0;
|
|
break;
|
|
}
|
|
}
|
|
if (!copy) {
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Can't find one. Must copy.
|
|
*/
|
|
rc = fcb_append(&cf->cf_fcb, loc1.loc.fe_data_len, &loc2.loc);
|
|
if (rc) {
|
|
continue;
|
|
}
|
|
|
|
rc = settings_line_entry_copy(&loc2, 0, &loc1, 0,
|
|
loc1.loc.fe_data_len);
|
|
if (rc) {
|
|
continue;
|
|
}
|
|
rc = fcb_append_finish(&cf->cf_fcb, &loc2.loc);
|
|
|
|
if (rc != 0) {
|
|
LOG_ERR("Failed to finish fcb_append (%d)", rc);
|
|
}
|
|
}
|
|
rc = fcb_rotate(&cf->cf_fcb);
|
|
|
|
if (rc != 0) {
|
|
LOG_ERR("Failed to fcb rotate (%d)", rc);
|
|
}
|
|
}
|
|
|
|
static size_t get_len_cb(void *ctx)
|
|
{
|
|
struct fcb_entry_ctx *entry_ctx = ctx;
|
|
|
|
return entry_ctx->loc.fe_data_len;
|
|
}
|
|
|
|
static int write_handler(void *ctx, off_t off, char const *buf, size_t len)
|
|
{
|
|
struct fcb_entry_ctx *entry_ctx = ctx;
|
|
|
|
return flash_area_write(entry_ctx->fap,
|
|
FCB_ENTRY_FA_DATA_OFF(entry_ctx->loc) + off,
|
|
buf, len);
|
|
}
|
|
|
|
/* ::csi_save implementation */
|
|
static int settings_fcb_save_priv(struct settings_store *cs, const char *name,
|
|
const char *value, size_t val_len)
|
|
{
|
|
struct settings_fcb *cf = CONTAINER_OF(cs, struct settings_fcb, cf_store);
|
|
struct fcb_entry_ctx loc;
|
|
int len;
|
|
int rc = -EINVAL;
|
|
int i;
|
|
|
|
if (!name) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
len = settings_line_len_calc(name, val_len);
|
|
|
|
for (i = 0; i < cf->cf_fcb.f_sector_cnt; i++) {
|
|
rc = fcb_append(&cf->cf_fcb, len, &loc.loc);
|
|
if (rc != -ENOSPC) {
|
|
break;
|
|
}
|
|
|
|
/* FCB can compress up to cf->cf_fcb.f_sector_cnt - 1 times. */
|
|
if (i < (cf->cf_fcb.f_sector_cnt - 1)) {
|
|
settings_fcb_compress(cf);
|
|
}
|
|
}
|
|
if (rc) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
loc.fap = cf->cf_fcb.fap;
|
|
|
|
rc = settings_line_write(name, value, val_len, 0, (void *)&loc);
|
|
|
|
if (rc != -EIO) {
|
|
i = fcb_append_finish(&cf->cf_fcb, &loc.loc);
|
|
if (!rc) {
|
|
rc = i;
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static int settings_fcb_save(struct settings_store *cs, const char *name,
|
|
const char *value, size_t val_len)
|
|
{
|
|
struct settings_line_dup_check_arg cdca;
|
|
|
|
if (val_len > 0 && value == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* Check if we're writing the same value again.
|
|
*/
|
|
cdca.name = name;
|
|
cdca.val = (char *)value;
|
|
cdca.is_dup = 0;
|
|
cdca.val_len = val_len;
|
|
settings_fcb_load_priv(cs, settings_line_dup_check_cb, &cdca, false);
|
|
if (cdca.is_dup == 1) {
|
|
return 0;
|
|
}
|
|
return settings_fcb_save_priv(cs, name, value, val_len);
|
|
}
|
|
|
|
void settings_mount_fcb_backend(struct settings_fcb *cf)
|
|
{
|
|
uint8_t rbs;
|
|
|
|
rbs = cf->cf_fcb.f_align;
|
|
|
|
settings_line_io_init(read_handler, write_handler, get_len_cb, rbs);
|
|
}
|
|
|
|
int settings_backend_init(void)
|
|
{
|
|
static struct flash_sector
|
|
settings_fcb_area[CONFIG_SETTINGS_FCB_NUM_AREAS + 1];
|
|
static struct settings_fcb config_init_settings_fcb = {
|
|
.cf_fcb.f_magic = CONFIG_SETTINGS_FCB_MAGIC,
|
|
.cf_fcb.f_sectors = settings_fcb_area,
|
|
};
|
|
uint32_t cnt = sizeof(settings_fcb_area) /
|
|
sizeof(settings_fcb_area[0]);
|
|
int rc;
|
|
const struct flash_area *fap;
|
|
|
|
rc = flash_area_get_sectors(settings_fcb_get_flash_area(), &cnt,
|
|
settings_fcb_area);
|
|
if (rc != 0 && rc != -ENOMEM) {
|
|
return rc;
|
|
}
|
|
|
|
config_init_settings_fcb.cf_fcb.f_sector_cnt = cnt;
|
|
|
|
rc = settings_fcb_src(&config_init_settings_fcb);
|
|
if (rc != 0) {
|
|
rc = flash_area_open(settings_fcb_get_flash_area(), &fap);
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
|
|
rc = flash_area_flatten(fap, 0, fap->fa_size);
|
|
flash_area_close(fap);
|
|
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
|
|
rc = settings_fcb_src(&config_init_settings_fcb);
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
rc = settings_fcb_dst(&config_init_settings_fcb);
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
|
|
settings_mount_fcb_backend(&config_init_settings_fcb);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void *settings_fcb_storage_get(struct settings_store *cs)
|
|
{
|
|
struct settings_fcb *cf = CONTAINER_OF(cs, struct settings_fcb, cf_store);
|
|
|
|
return &cf->cf_fcb;
|
|
}
|