/* * Copyright (c) 2018 Nordic Semiconductor ASA * Copyright (c) 2015 Runtime Inc * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include "settings/settings_fcb.h" #include "settings_priv.h" #include 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_erase(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_erase(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; }