blob: c95df2e2c787a50cf88821b399e2ddb9df31468d [file] [log] [blame]
/*
* 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) {
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;
}
#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) {
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;
}
#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)