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