|  | /* | 
|  | * 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; | 
|  | } |