431 lines
11 KiB
C
431 lines
11 KiB
C
/*
|
|
* 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
|
|
|
|
#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)
|