retention: Add retention system

Adds a retention system which builds on top of retained_mem
drivers to allow partitioning of areas and data integrity with
magic header prefixes and checksum of stored data.

Signed-off-by: Jamie McCrae <jamie.mccrae@nordicsemi.no>
This commit is contained in:
Jamie McCrae 2023-02-23 12:25:51 +00:00 committed by Carles Cufí
parent 7ad855c378
commit 64f4404481
7 changed files with 627 additions and 0 deletions

View file

@ -0,0 +1,66 @@
# Copyright (c) 2023 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
description: |
Retention subsystem area, which has a retained memory parent. Example
64-byte area with 2-byte prefix and 1-byte checksum with 61 usable bytes
for user storage:
sram@2003FFC0 {
compatible = "zephyr,memory-region", "mmio-sram";
reg = <0x2003FFC0 64>;
zephyr,memory-region = "RetainedMem";
status = "okay";
retainedmem {
compatible = "zephyr,retained-ram";
status = "okay";
#address-cells = <1>;
#size-cells = <1>;
retention0: retention@0 {
compatible = "zephyr,retention";
status = "okay";
reg = <0x0 0x40>;
prefix = [04 fa];
checksum = <1>;
};
};
};
compatible: "zephyr,retention"
include: base.yaml
properties:
"#address-cells":
const: 1
description: |
Address reg cell is for the offset of the area in parent node, can be
increased if multiple retention partitions are used or parts are
reserved.
"#size-cells":
const: 1
description: |
Size reg cell is for the size of the area, which includes sizes of
prefix and checksum (if enabled).
reg:
required: true
prefix:
description: |
An optional magic prefix, which indicates that the data has been set
(applies to the header of the data, reduces the available user data
size).
type: uint8-array
checksum:
description: |
An optional data verification checksum, which indicates that the data is
valid (appended to the footer of the data, reduces the available user
data size). Value is size in bytes (0 for none, 1 for 8-bit CRC, 2 for
16-bit CRC, 4 for 32-bit CRC). Default is to not use a checksum.
type: int
default: 0

View file

@ -0,0 +1,115 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief Public API for retention API
*/
#ifndef ZEPHYR_INCLUDE_RETENTION_
#define ZEPHYR_INCLUDE_RETENTION_
#include <stdint.h>
#include <stddef.h>
#include <sys/types.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/types.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Retention API
* @defgroup retention_api Retention API
* @ingroup retention
* @{
*/
typedef ssize_t (*retention_size_api)(const struct device *dev);
typedef int (*retention_is_valid_api)(const struct device *dev);
typedef int (*retention_read_api)(const struct device *dev, off_t offset, uint8_t *buffer,
size_t size);
typedef int (*retention_write_api)(const struct device *dev, off_t offset,
const uint8_t *buffer, size_t size);
typedef int (*retention_clear_api)(const struct device *dev);
struct retention_api {
retention_size_api size;
retention_is_valid_api is_valid;
retention_read_api read;
retention_write_api write;
retention_clear_api clear;
};
/**
* @brief Returns the size of the retention area.
*
* @param dev Retention device to use.
*
* @retval Positive value indicating size in bytes on success, else negative errno
* code.
*/
ssize_t retention_size(const struct device *dev);
/**
* @brief Checks if the underlying data in the retention area is valid or not.
*
* @param dev Retention device to use.
*
* @retval 1 If successful and data is valid.
* @retval 0 If data is not valid.
* @retval -ENOTSUP If there is no header/checksum configured for the retention area.
* @retval -errno Error code code.
*/
int retention_is_valid(const struct device *dev);
/**
* @brief Reads data from the retention area.
*
* @param dev Retention device to use.
* @param offset Offset to read data from.
* @param buffer Buffer to store read data in.
* @param size Size of data to read.
*
* @retval 0 If successful.
* @retval -errno Error code code.
*/
int retention_read(const struct device *dev, off_t offset, uint8_t *buffer, size_t size);
/**
* @brief Writes data to the retention area (underlying data does not need to be
* cleared prior to writing), once function returns with a success code, the
* data will be classed as valid if queried using retention_is_valid().
*
* @param dev Retention device to use.
* @param offset Offset to write data to.
* @param buffer Data to write.
* @param size Size of data to be written.
*
* @retval 0 on success else negative errno code.
*/
int retention_write(const struct device *dev, off_t offset, const uint8_t *buffer, size_t size);
/**
* @brief Clears all data in the retention area (sets it to 0)
*
* @param dev Retention device to use.
*
* @retval 0 on success else negative errno code.
*/
int retention_clear(const struct device *dev);
/**
* @}
*/
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_INCLUDE_RETENTION_ */

View file

@ -31,6 +31,7 @@ add_subdirectory_ifdef(CONFIG_INPUT input)
add_subdirectory_ifdef(CONFIG_JWT jwt) add_subdirectory_ifdef(CONFIG_JWT jwt)
add_subdirectory_ifdef(CONFIG_LORAWAN lorawan) add_subdirectory_ifdef(CONFIG_LORAWAN lorawan)
add_subdirectory_ifdef(CONFIG_NET_BUF net) add_subdirectory_ifdef(CONFIG_NET_BUF net)
add_subdirectory_ifdef(CONFIG_RETENTION retention)
add_subdirectory_ifdef(CONFIG_SETTINGS settings) add_subdirectory_ifdef(CONFIG_SETTINGS settings)
add_subdirectory_ifdef(CONFIG_SHELL shell) add_subdirectory_ifdef(CONFIG_SHELL shell)
add_subdirectory_ifdef(CONFIG_TIMING_FUNCTIONS timing) add_subdirectory_ifdef(CONFIG_TIMING_FUNCTIONS timing)

View file

@ -28,6 +28,7 @@ source "subsys/net/Kconfig"
source "subsys/pm/Kconfig" source "subsys/pm/Kconfig"
source "subsys/portability/Kconfig" source "subsys/portability/Kconfig"
source "subsys/random/Kconfig" source "subsys/random/Kconfig"
source "subsys/retention/Kconfig"
source "subsys/rtio/Kconfig" source "subsys/rtio/Kconfig"
source "subsys/sd/Kconfig" source "subsys/sd/Kconfig"
source "subsys/settings/Kconfig" source "subsys/settings/Kconfig"

View file

@ -0,0 +1,4 @@
# SPDX-License-Identifier: Apache-2.0
zephyr_library()
zephyr_library_sources(retention.c)

34
subsys/retention/Kconfig Normal file
View file

@ -0,0 +1,34 @@
# Copyright (c) 2023, Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
menuconfig RETENTION
bool "Retention support"
depends on CRC
depends on RETAINED_MEM
depends on DT_HAS_ZEPHYR_DATA_RETENTION_ENABLED
help
Enables support for the data retention system, which uses retained
memory drivers.
if RETENTION
config RETENTION_INIT_PRIORITY
int "Retention devices init priority"
default 86
help
Data retention devices initialization priority (must be higher than
init priorities for retained memory drivers.
config RETENTION_BUFFER_SIZE
int "Retention stack buffer sizes"
default 16
range 1 4096
help
Size of buffers (stack based) used when reading and writing data
from/to the retention device.
module = RETENTION
module-str = retention
source "subsys/logging/Kconfig.template.log_config"
endif # RETENTION

View file

@ -0,0 +1,406 @@
/*
* Copyright (c) 2023, Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT zephyr_retention
#include <string.h>
#include <sys/types.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/crc.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/retained_mem.h>
#include <zephyr/retention/retention.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(retention, CONFIG_RETENTION_LOG_LEVEL);
#define DATA_VALID_VALUE 1
enum {
CHECKSUM_NONE = 0,
CHECKSUM_CRC8,
CHECKSUM_CRC16,
CHECKSUM_UNUSED,
CHECKSUM_CRC32,
};
struct retention_data {
bool header_written;
#ifdef CONFIG_MULTITHREADING
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_MULTITHREADING
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_MULTITHREADING
struct retention_data *data = dev->data;
k_mutex_unlock(&data->lock);
#else
ARG_UNUSED(dev);
#endif
}
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) {
uint8_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;
}
static int retention_init(const struct device *dev)
{
const struct retention_config *config = dev->config;
#ifdef CONFIG_MULTITHREADING
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_MULTITHREADING
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;
struct retention_data *data = dev->data;
int rc = 0;
uint8_t buffer[CONFIG_RETENTION_BUFFER_SIZE];
off_t pos;
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;
}
if (config->prefix_len != 0) {
/* Check magic header is present at the start of the section */
pos = 0;
while (pos < config->prefix_len) {
uint8_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;
}
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;
}
}
/* 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;
struct retention_data *data = dev->data;
int rc;
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;
}
/* 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;
}
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));
}
}
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)