Files
luban-lite/bsp/examples/test-spinor/fal_power_loss_protection.c
刘可亮 3e10f578d3 v1.2.2
2025-10-21 13:59:50 +08:00

390 lines
12 KiB
C

/*
* Copyright (c) 2025, ArtInChip Technology Co., Ltd
*
* SPDX-License-Identifier: Apache-2.0
*
* Authors: Cui Jiawei <jiawei.cui@artinchip.com>
*/
#include <fal.h>
#include <rtthread.h>
#include <rtdevice.h>
#include <string.h>
#include <finsh.h>
#include <aic_core.h>
#include <aic_crc32.h>
#define DEMO_PARTITION_NAME "data" /* Flash partition name */
#define PARTITION_SIZE 0x2000 /* Size per AB data backup */
#define A_OFFSET 0x0 /* data backup A offset */
#define B_OFFSET PARTITION_SIZE /* data backup B offset */
#define DATA_LEN_SIZE sizeof(uint32_t)
#define VERSION_SIZE sizeof(uint32_t)
#define CRC_SIZE sizeof(uint32_t)
#define DATA_SIZE \
0x20 /* It should be smaller than (PARTITION_SIZE-VERSION_SIZE-CRC_SIZE-DATA_LEN_SIZE) */
#define CRC_KEY 0x123
/* Data structure for each data backup */
typedef struct {
uint32_t data_len; /* data_len */
uint32_t version; /* Version number */
uint32_t crc; /* CRC32 checksum */
uint8_t data[DATA_SIZE]; /* Actual data */
} __attribute__((aligned(4))) partition_data_t;
static const struct fal_partition *s_part_dev = NULL;
static partition_data_t *s_part_data = NULL;
static u8 *s_write_buf = NULL;
static u8 *s_read_buf = NULL;
/**
* @brief Checks if the given backup data is valid.
* @param part Pointer to a constant partition_data_t structure to be validated.
* @return int Returns 1 if the backup data is valid, 0 otherwise.
*/
static int is_valid(const partition_data_t *part)
{
return (part->version != 0xFFFFFFFF) &&
(crc32(CRC_KEY, part->data, part->data_len) == part->crc);
}
static void data_log(char *tag)
{
u32 i;
rt_kprintf("[%s]:data len=%u, version=%u, crc=%u, data:\n", tag, s_part_data->data_len,
s_part_data->version, s_part_data->crc);
for (i = 0; i < s_part_data->data_len; i++) {
if (i && (i % 16) == 0)
rt_kprintf("\n");
rt_kprintf("%02x ", s_part_data->data[i]);
}
rt_kprintf("\n\n");
}
/**
* @brief Retrieves the latest valid data backup and its version.
*
* This function reads and checks the validity of two data backup (A and B). It then determines
* which data backup is the latest valid one and updates the provided pointers with the active data backup
* index and the corresponding version number.
*
* @param latest_idx Pointer to where the index of the latest data backup (0 for A, 1 for B) will be stored.
* @param version Pointer to where the version number of the latest valid data backup will be stored.
* @return int Returns 0 on success, -1 on failure.
*/
static int get_latest_data(int *latest_idx, u32 *version)
{
uint32_t a_ver = 0, b_ver = 0;
int a_valid = 0, b_valid = 0, ret;
/* Check data backup A */
ret = fal_partition_read(s_part_dev, A_OFFSET, (u8 *)s_part_data, sizeof(partition_data_t));
if (ret != sizeof(partition_data_t)) {
rt_kprintf("Read data failed\n");
return -1;
}
a_valid = is_valid(s_part_data);
a_ver = a_valid ? s_part_data->version : 0;
rt_kprintf("a_valid=%s, a_version=%d, ", a_valid == 0 ? "false" : "true", a_ver);
/* Check data backup B */
ret = fal_partition_read(s_part_dev, B_OFFSET, (u8 *)s_part_data, sizeof(partition_data_t));
if (ret != sizeof(partition_data_t)) {
rt_kprintf("Read data failed\n");
return -1;
}
b_valid = is_valid(s_part_data);
b_ver = b_valid ? s_part_data->version : 0;
rt_kprintf("b_valid=%s, b_version=%d\n", b_valid == 0 ? "false" : "true", b_ver);
/* Select the complete and latest data backup. */
if (latest_idx != NULL) {
if (a_valid && b_valid) {
*latest_idx = (a_ver >= b_ver) ? 0 : 1;
} else if (a_valid) {
*latest_idx = 0;
} else {
/**
* If both A and B data backup are damaged, by default,
* the B data backup will be treated as the latest data backup.
*/
*latest_idx = 1;
}
rt_kprintf("latest part:%c", *latest_idx == 0 ? 'A' : 'B');
}
if (version != NULL) {
if (a_valid && b_valid) {
*version = (a_ver >= b_ver) ? a_ver : b_ver;
} else if (a_valid) {
*version = a_ver;
} else if (b_valid) {
*version = b_ver;
} else {
*version = 1;
}
rt_kprintf(" version:%u", *version);
}
rt_kprintf("\n");
return 0;
}
/**
* @brief Reads data from the latest data backup into a buffer.
*
* This function reads data from the latest available data backup (either A or B)
* into the provided buffer. It ensures that the read size does not exceed
* the predefined DATA_SIZE. If the latest data backup is invalid or the read size
* is too large, it returns an error.
*
* @param buf Pointer to the buffer where data will be read into.
* @param size The size of the data to be read.
* @return int Returns 0 on success, -1 on failure.
*/
static int read_data(uint8_t *buf, uint32_t size)
{
int latest_part, ret;
if (size > DATA_SIZE)
return -1;
ret = get_latest_data(&latest_part, NULL);
if (ret != 0)
return -1;
uint32_t offset = (latest_part == 0) ? A_OFFSET : B_OFFSET;
ret = fal_partition_read(s_part_dev, offset, (u8 *)s_part_data, sizeof(partition_data_t));
if (ret != sizeof(partition_data_t)) {
rt_kprintf("Read data failed\n");
return -1;
}
if (is_valid(s_part_data)) {
rt_kprintf("Data is valid\n");
} else {
rt_kprintf("ERR: Data is not valid\n");
return -1;
}
rt_kprintf("read data from part %c success\n", offset == A_OFFSET ? 'A' : 'B');
data_log("read data");
memcpy(buf, s_part_data->data, size);
return 0;
}
/**
* @brief Writes data to the appropriate data backup with CRC verification.
*
* This function writes data to the latest available data backup (either A or B)
* after preparing it with a version number and CRC checksum. It ensures that
* the write size does not exceed the predefined DATA_SIZE. If the write operation
* fails, it returns an error.
*
* @param data Pointer to the data to be written.
* @param size The size of the data to be written.
* @return int Returns 0 on success, -1 on failure.
*/
static int write_data(const uint8_t *data, uint32_t size)
{
int latest_part, ret;
u32 version, offset;
partition_data_t verify;
if (size > DATA_SIZE)
return -1;
/* Write to the old data backup.To calculate the offset */
get_latest_data(&latest_part, &version);
offset = (latest_part == 0) ? B_OFFSET : A_OFFSET;
/* Prepare data with CRC */
s_part_data->version = (latest_part >= 0) ? version + 1 : 1;
memset(s_part_data->data, 0, DATA_SIZE);
memcpy(s_part_data->data, data, size);
s_part_data->crc = crc32(CRC_KEY, s_part_data->data, DATA_SIZE);
s_part_data->data_len = DATA_SIZE;
/* Erase and write */
ret = fal_partition_erase(s_part_dev, offset, PARTITION_SIZE);
if (ret != PARTITION_SIZE) {
rt_kprintf("Erase data failed\n");
return -1;
}
ret = fal_partition_write(s_part_dev, offset, (u8 *)s_part_data, sizeof(partition_data_t));
if (ret != sizeof(partition_data_t)) {
rt_kprintf("Write data failed\n");
return -1;
}
/* Verify data */
verify = *s_part_data;
ret = fal_partition_read(s_part_dev, offset, (u8 *)s_part_data, sizeof(partition_data_t));
if (ret != sizeof(partition_data_t)) {
rt_kprintf("Read data failed\n");
return -1;
}
if (memcmp(&verify, s_part_data, sizeof(partition_data_t)) == 0) {
rt_kprintf("write data into part %c success\n", offset == A_OFFSET ? 'A' : 'B');
data_log("write data");
} else {
rt_kprintf("write data into part %c failed\n", offset == A_OFFSET ? 'A' : 'B');
return -1;
}
return 0;
}
static int erase_latest_part()
{
int latest_part, ret;
u32 offset;
get_latest_data(&latest_part, NULL);
offset = (latest_part == 0) ? A_OFFSET : B_OFFSET;
/* Erase */
ret = fal_partition_erase(s_part_dev, offset, PARTITION_SIZE);
if (ret != PARTITION_SIZE) {
rt_kprintf("Erase data failed\n");
return -1;
}
rt_kprintf("erase data from part %c success\n\n", offset == A_OFFSET ? 'A' : 'B');
return 0;
}
/**
* @brief Initializes the storage data backup and prepares initial data.
*
* This function initializes the storage by finding the specified data backup,
* allocating necessary memory buffers, and preparing initial data for both
* data backup (A and B). It sets up version numbers and CRC checksums for
* verification purposes. If any step fails, it returns an error.
*
* @return int Returns 0 on success, -1 on failure.
*/
static int storage_init(void)
{
s_part_dev = fal_partition_find(DEMO_PARTITION_NAME);
if (!s_part_dev)
return -1;
rt_kprintf("Check the initial AB data backup.\n");
fal_partition_read(s_part_dev, A_OFFSET, (u8 *)s_part_data, sizeof(partition_data_t));
if (!is_valid(s_part_data)) {
memset(s_part_data->data, 'A', DATA_SIZE);
s_part_data->crc = crc32(CRC_KEY, s_part_data->data, DATA_SIZE);
s_part_data->version = 1;
s_part_data->data_len = DATA_SIZE;
fal_partition_erase(s_part_dev, A_OFFSET, PARTITION_SIZE);
fal_partition_write(s_part_dev, A_OFFSET, (u8 *)s_part_data, sizeof(partition_data_t));
data_log("A part data error, init");
} else {
data_log("found A part data");
}
fal_partition_read(s_part_dev, B_OFFSET, (u8 *)s_part_data, sizeof(partition_data_t));
if (!is_valid(s_part_data)) {
memset(s_part_data->data, 'B', DATA_SIZE);
s_part_data->crc = crc32(CRC_KEY, s_part_data->data, DATA_SIZE);
s_part_data->version = 2;
s_part_data->data_len = DATA_SIZE;
fal_partition_erase(s_part_dev, B_OFFSET, PARTITION_SIZE);
fal_partition_write(s_part_dev, B_OFFSET, (u8 *)s_part_data, sizeof(partition_data_t));
data_log("B part data error, init");
} else {
data_log("found B part data");
}
/* prepare some write data */
for (int i = 0; i < DATA_SIZE; i++) {
s_write_buf[i] = i % 256;
}
return 0;
}
/*
* This test simulates power loss causing the latest backup to be corrupted,
* verifying if the system can correctly fall back to the alternate backup and recover data.
*/
static void fal_pwr_loss(void)
{
int ret;
s_part_data = (partition_data_t *)rt_malloc(sizeof(partition_data_t));
if (!s_part_data) {
rt_kprintf("Error: No memory for part data!\n");
goto free;
}
s_write_buf = (u8 *)rt_malloc(DATA_SIZE);
if (!s_write_buf) {
rt_kprintf("Error: No memory for write buffer!\n");
goto free;
}
s_read_buf = (u8 *)rt_malloc(DATA_SIZE);
if (!s_read_buf) {
rt_kprintf("Error: No memory for read buffer!\n");
goto free;
}
ret = storage_init();
if (ret) {
rt_kprintf("storage init failed!\n");
goto free;
}
/**
* Read the data. Since the data version in data backup B is higher,
* the data from data backup B will be read.
*/
rt_kprintf("Read the latest version of the partition data. The expected is B.\n");
ret = read_data(s_read_buf, DATA_SIZE);
if (ret) {
rt_kprintf("read data failed!\n");
goto free;
}
rt_kprintf("To simulate backup data corruption or power off, erase the latest data backup.\n");
ret = erase_latest_part();
if (ret) {
rt_kprintf("erase data failed!\n");
goto free;
}
/**
* Read the data backup. Since the latest data backup (B data backup) is damaged (erased),
* it will read from data backup A.
*/
rt_kprintf("Read the backup data. The expected is A.\n");
ret = read_data(s_read_buf, DATA_SIZE);
if (ret) {
rt_kprintf("read data failed!\n");
goto free;
}
/* Write to the data backup, prioritizing the damaged data backup (B data backup). */
rt_kprintf("Write data. The expected is B.\n");
ret = write_data(s_write_buf, DATA_SIZE);
if (ret) {
rt_kprintf("write data failed!\n");
goto free;
}
free:
if (s_part_data != NULL)
rt_free(s_part_data);
if (s_write_buf != NULL)
rt_free(s_write_buf);
if (s_read_buf != NULL)
rt_free(s_read_buf);
return;
}
MSH_CMD_EXPORT(fal_pwr_loss, FAL power loss protection demo);