/* * Copyright (c) 2019 Laczen * Copyright (c) 2019 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include "settings/settings_nvs.h" #include #include "settings_priv.h" #include #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 struct settings_nvs_read_fn_arg { struct nvs_fs *fs; uint16_t id; }; static int settings_nvs_load(struct settings_store *cs, const struct settings_load_arg *arg); static int settings_nvs_save(struct settings_store *cs, const char *name, const char *value, size_t val_len); static void *settings_nvs_storage_get(struct settings_store *cs); static struct settings_store_itf settings_nvs_itf = { .csi_load = settings_nvs_load, .csi_save = settings_nvs_save, .csi_storage_get = settings_nvs_storage_get }; static ssize_t settings_nvs_read_fn(void *back_end, void *data, size_t len) { struct settings_nvs_read_fn_arg *rd_fn_arg; ssize_t rc; rd_fn_arg = (struct settings_nvs_read_fn_arg *)back_end; rc = nvs_read(rd_fn_arg->fs, rd_fn_arg->id, data, len); if (rc > (ssize_t)len) { /* nvs_read signals that not all bytes were read * align read len to what was requested */ rc = len; } return rc; } int settings_nvs_src(struct settings_nvs *cf) { cf->cf_store.cs_itf = &settings_nvs_itf; settings_src_register(&cf->cf_store); return 0; } int settings_nvs_dst(struct settings_nvs *cf) { cf->cf_store.cs_itf = &settings_nvs_itf; settings_dst_register(&cf->cf_store); return 0; } #if CONFIG_SETTINGS_NVS_NAME_CACHE #define SETTINGS_NVS_CACHE_OVFL(cf) ((cf)->cache_total > ARRAY_SIZE((cf)->cache)) static void settings_nvs_cache_add(struct settings_nvs *cf, const char *name, uint16_t name_id) { uint16_t name_hash = crc16_ccitt(0xffff, name, strlen(name)); cf->cache[cf->cache_next].name_hash = name_hash; cf->cache[cf->cache_next++].name_id = name_id; cf->cache_next %= CONFIG_SETTINGS_NVS_NAME_CACHE_SIZE; } static uint16_t settings_nvs_cache_match(struct settings_nvs *cf, const char *name, char *rdname, size_t len) { uint16_t name_hash = crc16_ccitt(0xffff, name, strlen(name)); int rc; for (int i = 0; i < CONFIG_SETTINGS_NVS_NAME_CACHE_SIZE; i++) { if (cf->cache[i].name_hash != name_hash) { continue; } if (cf->cache[i].name_id <= NVS_NAMECNT_ID) { continue; } rc = nvs_read(&cf->cf_nvs, cf->cache[i].name_id, rdname, len); if (rc < 0) { continue; } rdname[rc] = '\0'; if (strcmp(name, rdname)) { continue; } return cf->cache[i].name_id; } return NVS_NAMECNT_ID; } #endif /* CONFIG_SETTINGS_NVS_NAME_CACHE */ static int settings_nvs_load(struct settings_store *cs, const struct settings_load_arg *arg) { int ret = 0; struct settings_nvs *cf = CONTAINER_OF(cs, struct settings_nvs, cf_store); struct settings_nvs_read_fn_arg read_fn_arg; char name[SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN + 1]; char buf; ssize_t rc1, rc2; uint16_t name_id = NVS_NAMECNT_ID; #if CONFIG_SETTINGS_NVS_NAME_CACHE uint16_t cached = 0; cf->loaded = false; #endif name_id = cf->last_name_id + 1; while (1) { name_id--; if (name_id == NVS_NAMECNT_ID) { #if CONFIG_SETTINGS_NVS_NAME_CACHE cf->loaded = true; cf->cache_total = cached; #endif break; } /* In the NVS backend, each setting item is stored in two NVS * entries one for the setting's name and one with the * setting's value. */ rc1 = nvs_read(&cf->cf_nvs, name_id, &name, sizeof(name)); rc2 = nvs_read(&cf->cf_nvs, name_id + NVS_NAME_ID_OFFSET, &buf, sizeof(buf)); if ((rc1 <= 0) && (rc2 <= 0)) { /* Settings largest ID in use is invalid due to * reset, power failure or partition overflow. * Decrement it and check the next ID in subsequent * iteration. */ if (name_id == cf->last_name_id) { cf->last_name_id--; nvs_write(&cf->cf_nvs, NVS_NAMECNT_ID, &cf->last_name_id, sizeof(uint16_t)); } continue; } if ((rc1 <= 0) || (rc2 <= 0)) { /* Settings item is not stored correctly in the NVS. * NVS entry for its name or value is either missing * or deleted. Clean dirty entries to make space for * future settings item. */ nvs_delete(&cf->cf_nvs, name_id); nvs_delete(&cf->cf_nvs, name_id + NVS_NAME_ID_OFFSET); if (name_id == cf->last_name_id) { cf->last_name_id--; nvs_write(&cf->cf_nvs, NVS_NAMECNT_ID, &cf->last_name_id, sizeof(uint16_t)); } continue; } /* Found a name, this might not include a trailing \0 */ name[rc1] = '\0'; read_fn_arg.fs = &cf->cf_nvs; read_fn_arg.id = name_id + NVS_NAME_ID_OFFSET; #if CONFIG_SETTINGS_NVS_NAME_CACHE settings_nvs_cache_add(cf, name, name_id); cached++; #endif ret = settings_call_set_handler( name, rc2, settings_nvs_read_fn, &read_fn_arg, (void *)arg); if (ret) { break; } } return ret; } static int settings_nvs_save(struct settings_store *cs, const char *name, const char *value, size_t val_len) { struct settings_nvs *cf = CONTAINER_OF(cs, struct settings_nvs, cf_store); char rdname[SETTINGS_MAX_NAME_LEN + SETTINGS_EXTRA_LEN + 1]; uint16_t name_id, write_name_id; bool delete, write_name; int rc = 0; if (!name) { return -EINVAL; } /* Find out if we are doing a delete */ delete = ((value == NULL) || (val_len == 0)); #if CONFIG_SETTINGS_NVS_NAME_CACHE bool name_in_cache = false; name_id = settings_nvs_cache_match(cf, name, rdname, sizeof(rdname)); if (name_id != NVS_NAMECNT_ID) { write_name_id = name_id; write_name = false; name_in_cache = true; goto found; } #endif name_id = cf->last_name_id + 1; write_name_id = cf->last_name_id + 1; write_name = true; #if CONFIG_SETTINGS_NVS_NAME_CACHE /* We can skip reading NVS if we know that the cache wasn't overflowed. */ if (cf->loaded && !SETTINGS_NVS_CACHE_OVFL(cf)) { goto found; } #endif while (1) { name_id--; if (name_id == NVS_NAMECNT_ID) { break; } rc = nvs_read(&cf->cf_nvs, name_id, &rdname, sizeof(rdname)); if (rc < 0) { /* Error or entry not found */ if (rc == -ENOENT) { write_name_id = name_id; } continue; } rdname[rc] = '\0'; if (strcmp(name, rdname)) { continue; } if (!delete) { write_name_id = name_id; write_name = false; } goto found; } found: if (delete) { if (name_id == NVS_NAMECNT_ID) { return 0; } rc = nvs_delete(&cf->cf_nvs, name_id); if (rc >= 0) { rc = nvs_delete(&cf->cf_nvs, name_id + NVS_NAME_ID_OFFSET); } if (rc < 0) { return rc; } if (name_id == cf->last_name_id) { cf->last_name_id--; rc = nvs_write(&cf->cf_nvs, NVS_NAMECNT_ID, &cf->last_name_id, sizeof(uint16_t)); if (rc < 0) { /* Error: can't to store * the largest name ID in use. */ return rc; } } return 0; } /* No free IDs left. */ if (write_name_id == NVS_NAMECNT_ID + NVS_NAME_ID_OFFSET) { return -ENOMEM; } /* update the last_name_id and write to flash if required*/ if (write_name_id > cf->last_name_id) { cf->last_name_id = write_name_id; rc = nvs_write(&cf->cf_nvs, NVS_NAMECNT_ID, &cf->last_name_id, sizeof(uint16_t)); if (rc < 0) { return rc; } } /* write the value */ rc = nvs_write(&cf->cf_nvs, write_name_id + NVS_NAME_ID_OFFSET, value, val_len); if (rc < 0) { return rc; } /* write the name if required */ if (write_name) { rc = nvs_write(&cf->cf_nvs, write_name_id, name, strlen(name)); if (rc < 0) { return rc; } } #if CONFIG_SETTINGS_NVS_NAME_CACHE if (!name_in_cache) { settings_nvs_cache_add(cf, name, write_name_id); if (cf->loaded && !SETTINGS_NVS_CACHE_OVFL(cf)) { cf->cache_total++; } } #endif return 0; } /* Initialize the nvs backend. */ int settings_nvs_backend_init(struct settings_nvs *cf) { int rc; uint16_t last_name_id; cf->cf_nvs.flash_device = cf->flash_dev; if (cf->cf_nvs.flash_device == NULL) { return -ENODEV; } rc = nvs_mount(&cf->cf_nvs); if (rc) { return rc; } rc = nvs_read(&cf->cf_nvs, NVS_NAMECNT_ID, &last_name_id, sizeof(last_name_id)); if (rc < 0) { cf->last_name_id = NVS_NAMECNT_ID; } else { cf->last_name_id = last_name_id; } LOG_DBG("Initialized"); return 0; } int settings_backend_init(void) { static struct settings_nvs default_settings_nvs; int rc; uint16_t cnt = 0; size_t nvs_sector_size, nvs_size = 0; const struct flash_area *fa; struct flash_sector hw_flash_sector; uint32_t sector_cnt = 1; rc = flash_area_open(SETTINGS_PARTITION, &fa); if (rc) { return rc; } rc = flash_area_get_sectors(SETTINGS_PARTITION, §or_cnt, &hw_flash_sector); if (rc != 0 && rc != -ENOMEM) { return rc; } nvs_sector_size = CONFIG_SETTINGS_NVS_SECTOR_SIZE_MULT * hw_flash_sector.fs_size; if (nvs_sector_size > UINT16_MAX) { return -EDOM; } while (cnt < CONFIG_SETTINGS_NVS_SECTOR_COUNT) { nvs_size += nvs_sector_size; if (nvs_size > fa->fa_size) { break; } cnt++; } /* define the nvs file system using the page_info */ default_settings_nvs.cf_nvs.sector_size = nvs_sector_size; default_settings_nvs.cf_nvs.sector_count = cnt; default_settings_nvs.cf_nvs.offset = fa->fa_off; default_settings_nvs.flash_dev = fa->fa_dev; rc = settings_nvs_backend_init(&default_settings_nvs); if (rc) { return rc; } rc = settings_nvs_src(&default_settings_nvs); if (rc) { return rc; } rc = settings_nvs_dst(&default_settings_nvs); return rc; } static void *settings_nvs_storage_get(struct settings_store *cs) { struct settings_nvs *cf = CONTAINER_OF(cs, struct settings_nvs, cf_store); return &cf->cf_nvs; }