/* * Copyright (c) 2023, Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT zephyr_retention #include #include #include #include #include #include #include #include #include #include LOG_MODULE_REGISTER(retention, CONFIG_RETENTION_LOG_LEVEL); #define DATA_VALID_VALUE 1 #define INST_HAS_CHECKSUM(n) DT_INST_PROP(n, checksum) || #define INST_HAS_PREFIX(n) COND_CODE_1(DT_INST_NODE_HAS_PROP(n, prefix), (1), (0)) || #if (DT_INST_FOREACH_STATUS_OKAY(INST_HAS_CHECKSUM) 0) #define ANY_HAS_CHECKSUM #endif #if (DT_INST_FOREACH_STATUS_OKAY(INST_HAS_PREFIX) 0) #define ANY_HAS_PREFIX #endif enum { CHECKSUM_NONE = 0, CHECKSUM_CRC8, CHECKSUM_CRC16, CHECKSUM_UNUSED, CHECKSUM_CRC32, }; struct retention_data { bool header_written; #ifdef CONFIG_RETENTION_MUTEXES struct k_mutex lock; #endif }; struct retention_config { const struct device *parent; size_t offset; size_t size; size_t reserved_size; uint8_t checksum_size; uint8_t prefix_len; uint8_t prefix[]; }; static inline void retention_lock_take(const struct device *dev) { #ifdef CONFIG_RETENTION_MUTEXES struct retention_data *data = dev->data; k_mutex_lock(&data->lock, K_FOREVER); #else ARG_UNUSED(dev); #endif } static inline void retention_lock_release(const struct device *dev) { #ifdef CONFIG_RETENTION_MUTEXES struct retention_data *data = dev->data; k_mutex_unlock(&data->lock); #else ARG_UNUSED(dev); #endif } #ifdef ANY_HAS_CHECKSUM static int retention_checksum(const struct device *dev, uint32_t *output) { const struct retention_config *config = dev->config; int rc = -ENOSYS; if (config->checksum_size == CHECKSUM_CRC8 || config->checksum_size == CHECKSUM_CRC16 || config->checksum_size == CHECKSUM_CRC32) { size_t pos = config->offset + config->prefix_len; size_t end = config->offset + config->size - config->checksum_size; uint8_t buffer[CONFIG_RETENTION_BUFFER_SIZE]; *output = 0; while (pos < end) { uint16_t read_size = MIN((end - pos), sizeof(buffer)); rc = retained_mem_read(config->parent, pos, buffer, read_size); if (rc < 0) { goto finish; } if (config->checksum_size == CHECKSUM_CRC8) { *output = (uint32_t)crc8(buffer, read_size, 0x12, (uint8_t)*output, false); } else if (config->checksum_size == CHECKSUM_CRC16) { *output = (uint32_t)crc16_itu_t((uint16_t)*output, buffer, read_size); } else if (config->checksum_size == CHECKSUM_CRC32) { *output = crc32_ieee_update(*output, buffer, read_size); } pos += read_size; } } finish: return rc; } #endif static int retention_init(const struct device *dev) { const struct retention_config *config = dev->config; #ifdef CONFIG_RETENTION_MUTEXES struct retention_data *data = dev->data; #endif ssize_t area_size; if (!device_is_ready(config->parent)) { LOG_ERR("Parent device is not ready"); return -ENODEV; } /* Ensure backend has a large enough storage area for the requirements of * this retention area */ area_size = retained_mem_size(config->parent); if (area_size < 0) { LOG_ERR("Parent initialisation failure: %d", area_size); return area_size; } if ((config->offset + config->size) > area_size) { /* Backend storage is insufficient */ LOG_ERR("Underlying area size is insufficient, requires: 0x%x, has: 0x%x", (config->offset + config->size), area_size); return -EINVAL; } #ifdef CONFIG_RETENTION_MUTEXES k_mutex_init(&data->lock); #endif return 0; } ssize_t retention_size(const struct device *dev) { const struct retention_config *config = dev->config; return (config->size - config->reserved_size); } int retention_is_valid(const struct device *dev) { const struct retention_config *config = dev->config; int rc = 0; retention_lock_take(dev); /* If neither the header or checksum are enabled, return a not supported error */ if (config->prefix_len == 0 && config->checksum_size == 0) { rc = -ENOTSUP; goto finish; } #ifdef ANY_HAS_PREFIX if (config->prefix_len != 0) { /* Check magic header is present at the start of the section */ struct retention_data *data = dev->data; uint8_t buffer[CONFIG_RETENTION_BUFFER_SIZE]; off_t pos = 0; while (pos < config->prefix_len) { uint16_t read_size = MIN((config->prefix_len - pos), sizeof(buffer)); rc = retained_mem_read(config->parent, (config->offset + pos), buffer, read_size); if (rc < 0) { goto finish; } if (memcmp(&config->prefix[pos], buffer, read_size) != 0) { /* If the magic header does not match, do not check the rest of * the validity of the data, assume it is invalid */ data->header_written = false; rc = 0; goto finish; } pos += read_size; } /* Header already exists so no need to re-write it again */ data->header_written = true; } #endif #ifdef ANY_HAS_CHECKSUM if (config->checksum_size != 0) { /* Check the checksum validity, for this all the data must be read out */ uint32_t checksum = 0; uint32_t expected_checksum = 0; ssize_t data_size = config->size - config->checksum_size; rc = retention_checksum(dev, &checksum); if (rc < 0) { goto finish; } if (config->checksum_size == CHECKSUM_CRC8) { uint8_t read_checksum; rc = retained_mem_read(config->parent, (config->offset + data_size), (void *)&read_checksum, sizeof(read_checksum)); expected_checksum = (uint32_t)read_checksum; } else if (config->checksum_size == CHECKSUM_CRC16) { uint16_t read_checksum; rc = retained_mem_read(config->parent, (config->offset + data_size), (void *)&read_checksum, sizeof(read_checksum)); expected_checksum = (uint32_t)read_checksum; } else if (config->checksum_size == CHECKSUM_CRC32) { rc = retained_mem_read(config->parent, (config->offset + data_size), (void *)&expected_checksum, sizeof(expected_checksum)); } if (rc < 0) { goto finish; } if (checksum != expected_checksum) { goto finish; } } #endif /* At this point, checks have passed (if enabled), mark data as being valid */ rc = DATA_VALID_VALUE; finish: retention_lock_release(dev); return rc; } int retention_read(const struct device *dev, off_t offset, uint8_t *buffer, size_t size) { const struct retention_config *config = dev->config; int rc; if (offset < 0 || ((size_t)offset + size) > (config->size - config->reserved_size)) { /* Disallow reading past the virtual data size or before it */ return -EINVAL; } retention_lock_take(dev); rc = retained_mem_read(config->parent, (config->offset + config->prefix_len + (size_t)offset), buffer, size); retention_lock_release(dev); return rc; } int retention_write(const struct device *dev, off_t offset, const uint8_t *buffer, size_t size) { const struct retention_config *config = dev->config; int rc; #ifdef ANY_HAS_PREFIX struct retention_data *data = dev->data; #endif retention_lock_take(dev); if (offset < 0 || ((size_t)offset + size) > (config->size - config->reserved_size)) { /* Disallow writing past the virtual data size or before it */ rc = -EINVAL; goto finish; } rc = retained_mem_write(config->parent, (config->offset + config->prefix_len + (size_t)offset), buffer, size); if (rc < 0) { goto finish; } #ifdef ANY_HAS_PREFIX /* Write optional header and footer information, these are done last to ensure data * validity before marking it as being valid */ if (config->prefix_len != 0 && data->header_written == false) { rc = retained_mem_write(config->parent, config->offset, (void *)config->prefix, config->prefix_len); if (rc < 0) { goto finish; } data->header_written = true; } #endif #ifdef ANY_HAS_CHECKSUM if (config->checksum_size != 0) { /* Generating a checksum requires reading out all the data in the region */ uint32_t checksum = 0; rc = retention_checksum(dev, &checksum); if (rc < 0) { goto finish; } if (config->checksum_size == CHECKSUM_CRC8) { uint8_t output_checksum = (uint8_t)checksum; rc = retained_mem_write(config->parent, (config->offset + config->size - config->checksum_size), (void *)&output_checksum, sizeof(output_checksum)); } else if (config->checksum_size == CHECKSUM_CRC16) { uint16_t output_checksum = (uint16_t)checksum; rc = retained_mem_write(config->parent, (config->offset + config->size - config->checksum_size), (void *)&output_checksum, sizeof(output_checksum)); } else if (config->checksum_size == CHECKSUM_CRC32) { rc = retained_mem_write(config->parent, (config->offset + config->size - config->checksum_size), (void *)&checksum, sizeof(checksum)); } } #endif finish: retention_lock_release(dev); return rc; } int retention_clear(const struct device *dev) { const struct retention_config *config = dev->config; struct retention_data *data = dev->data; int rc = 0; uint8_t buffer[CONFIG_RETENTION_BUFFER_SIZE]; off_t pos = 0; memset(buffer, 0, sizeof(buffer)); retention_lock_take(dev); data->header_written = false; while (pos < config->size) { rc = retained_mem_write(config->parent, (config->offset + pos), buffer, MIN((config->size - pos), sizeof(buffer))); if (rc < 0) { goto finish; } pos += MIN((config->size - pos), sizeof(buffer)); } finish: retention_lock_release(dev); return rc; } static const struct retention_api retention_api = { .size = retention_size, .is_valid = retention_is_valid, .read = retention_read, .write = retention_write, .clear = retention_clear, }; #define RETENTION_DEVICE(inst) \ static struct retention_data \ retention_data_##inst = { \ .header_written = false, \ }; \ static const struct retention_config \ retention_config_##inst = { \ .parent = DEVICE_DT_GET(DT_PARENT(DT_INST(inst, DT_DRV_COMPAT))), \ .checksum_size = DT_INST_PROP(inst, checksum), \ .offset = DT_INST_REG_ADDR(inst), \ .size = DT_INST_REG_SIZE(inst), \ .reserved_size = (COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, prefix), \ (DT_INST_PROP_LEN(inst, prefix)), (0)) + \ DT_INST_PROP(inst, checksum)), \ .prefix_len = COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, prefix), \ (DT_INST_PROP_LEN(inst, prefix)), (0)), \ .prefix = DT_INST_PROP_OR(inst, prefix, {0}), \ }; \ DEVICE_DT_INST_DEFINE(inst, \ &retention_init, \ NULL, \ &retention_data_##inst, \ &retention_config_##inst, \ POST_KERNEL, \ CONFIG_RETENTION_INIT_PRIORITY, \ &retention_api); DT_INST_FOREACH_STATUS_OKAY(RETENTION_DEVICE)