| /* |
| * Copyright (c) 2019 Laczen |
| * Copyright (c) 2019 Nordic Semiconductor ASA |
| * Copyright (c) 2025 Analog Devices, Inc. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <psa/internal_trusted_storage.h> |
| #include <zephyr/settings/settings.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/psa/its_ids.h> |
| |
| LOG_MODULE_DECLARE(settings, CONFIG_SETTINGS_LOG_LEVEL); |
| |
| K_MUTEX_DEFINE(worker_mutex); |
| static struct k_work_delayable worker; |
| |
| struct setting_entry { |
| char name[SETTINGS_MAX_NAME_LEN]; |
| char value[SETTINGS_MAX_VAL_LEN]; |
| size_t val_len; |
| }; |
| |
| static struct setting_entry entries[CONFIG_SETTINGS_TFM_ITS_NUM_ENTRIES]; |
| static int entries_count; |
| |
| static int settings_its_load(struct settings_store *cs, const struct settings_load_arg *arg); |
| static int settings_its_save(struct settings_store *cs, const char *name, const char *value, |
| size_t val_len); |
| |
| static const struct settings_store_itf settings_its_itf = { |
| .csi_load = settings_its_load, |
| .csi_save = settings_its_save, |
| }; |
| |
| static struct settings_store default_settings_its = {.cs_itf = &settings_its_itf}; |
| |
| /* Ensure Key configured max size does not exceed reserved Key range */ |
| BUILD_ASSERT(sizeof(entries) / CONFIG_TFM_ITS_MAX_ASSET_SIZE <= |
| ZEPHYR_PSA_SETTINGS_TFM_ITS_UID_RANGE_SIZE, |
| "entries array exceeds reserved ITS UID range"); |
| |
| static int store_entries(void) |
| { |
| psa_status_t status; |
| psa_storage_uid_t uid = ZEPHYR_PSA_SETTINGS_TFM_ITS_UID_RANGE_BEGIN; |
| size_t remaining = sizeof(entries); |
| size_t chunk_size = CONFIG_TFM_ITS_MAX_ASSET_SIZE; |
| const uint8_t *data_ptr = (const uint8_t *)&entries; |
| |
| /* |
| * Each ITS UID is treated like a sector. Data is written to each ITS node until |
| * that node is full, before incrementing the UID. This is done to minimize the |
| * number of allocated ITS nodes and to avoid wasting allocated bytes. |
| */ |
| while (remaining > 0) { |
| size_t write_size = (remaining > chunk_size) ? chunk_size : remaining; |
| |
| status = psa_its_set(uid, write_size, data_ptr, PSA_STORAGE_FLAG_NONE); |
| if (status) { |
| LOG_ERR("Error storing %d bytes of metadata! Bytes Remaining: %d, status: " |
| "%d", |
| write_size, remaining, status); |
| return status; |
| } |
| |
| data_ptr += write_size; |
| remaining -= write_size; |
| uid++; |
| } |
| |
| LOG_DBG("ITS entries stored successfully - bytes_saved: %d num_entries: %d max_uid: %lld", |
| sizeof(entries), entries_count, uid); |
| |
| return 0; |
| } |
| |
| static int load_entries(void) |
| { |
| psa_status_t status; |
| size_t bytes_read; |
| psa_storage_uid_t uid = ZEPHYR_PSA_SETTINGS_TFM_ITS_UID_RANGE_BEGIN; |
| size_t remaining = sizeof(entries); |
| size_t chunk_size = CONFIG_TFM_ITS_MAX_ASSET_SIZE; |
| uint8_t *data_ptr = (uint8_t *)&entries; |
| |
| /* |
| * Each ITS UID is treated like a sector. Data is written to each ITS node until |
| * that node is full, before incrementing the UID. This is done to minimize the |
| * number of allocated ITS nodes and to avoid wasting allocated bytes. |
| */ |
| while (remaining > 0) { |
| size_t to_read = (remaining > chunk_size) ? chunk_size : remaining; |
| |
| status = psa_its_get(uid, 0, to_read, data_ptr, &bytes_read); |
| if (status) { |
| return status; |
| } |
| |
| data_ptr += bytes_read; |
| remaining -= bytes_read; |
| uid++; |
| } |
| |
| for (int i = 0; i < CONFIG_SETTINGS_TFM_ITS_NUM_ENTRIES; i++) { |
| if (strnlen(entries[i].name, SETTINGS_MAX_NAME_LEN) != 0) { |
| entries_count++; |
| } |
| } |
| |
| LOG_DBG("ITS entries restored successfully - bytes_loaded: %d, num_entries: %d", |
| sizeof(entries), entries_count); |
| |
| return 0; |
| } |
| |
| /* void *back_end is the index of the entry in metadata entries struct */ |
| static ssize_t settings_its_read_fn(void *back_end, void *data, size_t len) |
| { |
| int index = *(int *)back_end; |
| |
| LOG_DBG("ITS Read - index: %d", index); |
| |
| if (index < 0 || index >= CONFIG_SETTINGS_TFM_ITS_NUM_ENTRIES) { |
| LOG_ERR("Invalid index %d in ITS metadata", index); |
| return 0; |
| } |
| |
| memcpy(data, entries[index].value, len); |
| |
| /* |
| * Callback expects return value of the number of bytes read |
| */ |
| return entries[index].val_len; |
| } |
| |
| static int settings_its_load(struct settings_store *cs, const struct settings_load_arg *arg) |
| { |
| int ret; |
| |
| for (int i = 0; i < entries_count; i++) { |
| if (strnlen(entries[i].name, SETTINGS_MAX_NAME_LEN) != 0) { |
| /* |
| * Pass the key to the settings handler with it's index as an argument, |
| * to be read during callback function later. |
| */ |
| ret = settings_call_set_handler(entries[i].name, entries[i].val_len, |
| settings_its_read_fn, (void *)&i, |
| (void *)arg); |
| if (ret) { |
| return ret; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int settings_its_save(struct settings_store *cs, const char *name, const char *value, |
| size_t val_len) |
| { |
| if (entries_count >= CONFIG_SETTINGS_TFM_ITS_NUM_ENTRIES) { |
| LOG_ERR("%s: Max settings reached: %d", __func__, |
| CONFIG_SETTINGS_TFM_ITS_NUM_ENTRIES); |
| return -ENOMEM; |
| } |
| |
| if (val_len > SETTINGS_MAX_VAL_LEN) { |
| LOG_ERR("%s: Invalid settings size - val_len: %d", __func__, val_len); |
| return -EINVAL; |
| } |
| |
| int index; |
| bool delete; |
| |
| /* Find out if we are doing a delete */ |
| delete = ((value == NULL) || (val_len == 0)); |
| |
| /* Lock mutex before manipulating settings array */ |
| k_mutex_lock(&worker_mutex, K_FOREVER); |
| |
| /* |
| * Search metadata to see if entry already exists. Array is compacted, so first blank entry |
| * signals end of settings. |
| */ |
| for (index = 0; index < CONFIG_SETTINGS_TFM_ITS_NUM_ENTRIES; index++) { |
| if (strncmp(entries[index].name, name, SETTINGS_MAX_NAME_LEN) == 0) { |
| break; |
| } else if (entries[index].val_len == 0) { |
| |
| /* Setting already deleted */ |
| if (delete) { |
| LOG_DBG("%s: %s Already deleted!", __func__, name); |
| k_mutex_unlock(&worker_mutex); |
| return 0; |
| } |
| |
| /* New setting being entered */ |
| entries_count++; |
| break; |
| } |
| } |
| |
| LOG_DBG("ITS Save - index %d: name %s, val_len %d", index, name, val_len); |
| |
| if (delete) { |
| /* Clear metadata */ |
| memset(entries[index].name, 0, SETTINGS_MAX_NAME_LEN); |
| memset(entries[index].value, 0, SETTINGS_MAX_VAL_LEN); |
| entries[index].val_len = 0; |
| |
| /* If setting not at end of array, shift entries */ |
| if (index < entries_count - 1) { |
| memcpy(&entries[index], &entries[index + 1], |
| (entries_count - index - 1) * sizeof(struct setting_entry)); |
| /* Remove duplicate entry at end of array */ |
| memset(&entries[entries_count - 1], 0, sizeof(struct setting_entry)); |
| } |
| |
| entries_count--; |
| } else { |
| /* Update metadata */ |
| strncpy(entries[index].name, name, SETTINGS_MAX_NAME_LEN); |
| memcpy(entries[index].value, value, val_len); |
| entries[index].val_len = val_len; |
| } |
| |
| k_mutex_unlock(&worker_mutex); |
| k_work_schedule(&worker, K_MSEC(CONFIG_SETTINGS_TFM_ITS_LAZY_PERSIST_DELAY_MS)); |
| |
| return 0; |
| } |
| |
| void worker_persist_entries_struct_fn(struct k_work *work) |
| { |
| k_mutex_lock(&worker_mutex, K_FOREVER); |
| store_entries(); |
| k_mutex_unlock(&worker_mutex); |
| } |
| |
| int settings_backend_init(void) |
| { |
| psa_status_t status; |
| |
| /* Load ITS metadata */ |
| status = load_entries(); |
| |
| /* If resource DNE, we need to allocate it */ |
| if (status == PSA_ERROR_DOES_NOT_EXIST) { |
| status = store_entries(); |
| if (status) { |
| LOG_ERR("Error storing metadata in %s: (status %d)", __func__, status); |
| return -EIO; |
| } |
| } else if (status) { |
| LOG_ERR("Error loading metadata in %s: (status %d)", __func__, status); |
| return -EIO; |
| } |
| |
| settings_dst_register(&default_settings_its); |
| settings_src_register(&default_settings_its); |
| |
| k_work_init_delayable(&worker, worker_persist_entries_struct_fn); |
| |
| return 0; |
| } |