/* * Copyright (c) 2026, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * 2026-03-09 user first version */ #define DBG_TAG "BAK" #define DBG_LVL DBG_LOG #include #include #include #include #include #include "parabak.h" #include "ecode.h" #include "main.h" /* 引入需要备份的外部结构体 */ #include "ym310_protecl.h" // 提供 sys_info, dev_info, limit 的定义 /* 外部时间变量 */ extern struct rtc_time time_now; /*============================================================================= * 常量定义 *============================================================================*/ #define PARABAK_MAGIC 0x50415241 /* "PARA" */ #define PARABAK_VERSION 1 #define PARABAK_PART_SIZE 8192 /* 8KB */ #define HEADER_SECTOR_OFFSET 0 /* 头部扇区偏移 0 */ #define DATA_SECTOR_OFFSET 4096 /* 数据扇区偏移 4096 */ #define SECTOR_SIZE 4096 /*============================================================================= * 备份项表(内置,静态) *============================================================================*/ static const parabak_item_t parabak_items[PARABAK_ID_MAX] = { {PARABAK_ID_SYS_INFO, &sys_info, sizeof(SYS_INFO), "para_sys", EF_SYS_INFO}, {PARABAK_ID_DEV_INFO, &dev_info, sizeof(DEV_INFO), "para_dev", EF_DEV_INFO}, {PARABAK_ID_LIMIT, &limit, sizeof(LIMIT), "para_lim", EF_LIMIT}, }; /*============================================================================= * 静态变量(互斥锁、初始化标志) *============================================================================*/ static rt_mutex_t parabak_lock = RT_NULL; /* 保护数据操作的互斥锁 */ static rt_mutex_t init_mutex = RT_NULL; /* 保护初始化的互斥锁 */ static rt_bool_t initialized = RT_FALSE; /* 是否已初始化 */ /*============================================================================= * CRC32 计算(标准算法) *============================================================================*/ static uint32_t crc32_update(uint32_t crc, uint8_t data) { crc ^= data; for (int i = 0; i < 8; i++) { if (crc & 1) crc = (crc >> 1) ^ 0xEDB88320; else crc >>= 1; } return crc; } static uint32_t calc_crc32(const uint8_t *data, uint32_t len) { uint32_t crc = 0xFFFFFFFF; for (uint32_t i = 0; i < len; i++) crc = crc32_update(crc, data[i]); return crc ^ 0xFFFFFFFF; } /*============================================================================= * 时间戳转换(rtc_time -> parabak_tm) *============================================================================*/ static void rtc_to_parabak_tm(const struct rtc_time *src, struct parabak_tm *dst) { dst->tm_year = src->year - 2000; dst->tm_mon = src->month; dst->tm_mday = src->date; dst->tm_hour = src->hour; dst->tm_min = src->minute; dst->tm_sec = src->second; } static void get_timestamp(struct parabak_tm *ts) { rtc_to_parabak_tm(&time_now, ts); } /*============================================================================= * 分区操作辅助函数 *============================================================================*/ static const struct fal_partition *get_partition_by_id(parabak_id_t id) { if (id >= PARABAK_ID_MAX) return RT_NULL; const char *part_name = parabak_items[id].partition_name; return fal_partition_find(part_name); } static int erase_sector(const struct fal_partition *part, uint32_t offset) { if (fal_partition_erase(part, offset, SECTOR_SIZE) < 0) { LOG_E("erase sector at 0x%x failed", offset); return -RT_ERROR; } return RT_EOK; } static int write_header(const struct fal_partition *part, const parabak_header_t *hdr) { if (erase_sector(part, HEADER_SECTOR_OFFSET) != RT_EOK) return -RT_ERROR; if (fal_partition_write(part, HEADER_SECTOR_OFFSET, (uint8_t *)hdr, sizeof(parabak_header_t)) < 0) { LOG_E("write header failed"); return -RT_ERROR; } return RT_EOK; } static int read_header(const struct fal_partition *part, parabak_header_t *hdr) { if (fal_partition_read(part, HEADER_SECTOR_OFFSET, (uint8_t *)hdr, sizeof(parabak_header_t)) < 0) { LOG_E("read header failed"); return -RT_ERROR; } return RT_EOK; } static int write_data(const struct fal_partition *part, const uint8_t *data, uint32_t len) { if (len > SECTOR_SIZE) { LOG_E("data too large for sector"); return -RT_ERROR; } if (erase_sector(part, DATA_SECTOR_OFFSET) != RT_EOK) return -RT_ERROR; if (fal_partition_write(part, DATA_SECTOR_OFFSET, data, len) < 0) { LOG_E("write data failed"); return -RT_ERROR; } return RT_EOK; } static int read_data(const struct fal_partition *part, uint8_t *buf, uint32_t len) { if (len > SECTOR_SIZE) return -RT_ERROR; if (fal_partition_read(part, DATA_SECTOR_OFFSET, buf, len) < 0) { LOG_E("read data failed"); return -RT_ERROR; } return RT_EOK; } /*============================================================================= * 内部初始化核心函数(仅执行一次) *============================================================================*/ static int parabak_init_core(void) { parabak_lock = rt_mutex_create("parabak", RT_IPC_FLAG_FIFO); if (parabak_lock == RT_NULL) { LOG_E("create data mutex failed"); return -RT_ERROR; } LOG_I("parabak core initialized"); return RT_EOK; } /* 确保模块已初始化(线程安全,只执行一次) */ static int ensure_initialized(void) { if (initialized) return RT_EOK; if (init_mutex == RT_NULL) { init_mutex = rt_mutex_create("parabak_init", RT_IPC_FLAG_FIFO); if (init_mutex == RT_NULL) { LOG_E("create init mutex failed"); return -RT_ERROR; } } rt_mutex_take(init_mutex, RT_WAITING_FOREVER); if (!initialized) { int ret = parabak_init_core(); if (ret == RT_EOK) initialized = RT_TRUE; else LOG_E("parabak_init_core failed"); } rt_mutex_release(init_mutex); return initialized ? RT_EOK : -RT_ERROR; } /*============================================================================= * 公共 API(全部自动调用 ensure_initialized) *============================================================================*/ int parabak_backup(parabak_id_t id) { const parabak_item_t *item; const struct fal_partition *part; parabak_header_t hdr; uint32_t crc; int ret; if (ensure_initialized() != RT_EOK) { LOG_E("parabak not initialized"); return -RT_ERROR; } if (id >= PARABAK_ID_MAX) return -RT_ERROR; item = ¶bak_items[id]; part = get_partition_by_id(id); if (part == RT_NULL) { LOG_E("partition not found for id %d", id); return -RT_ERROR; } rt_mutex_take(parabak_lock, RT_WAITING_FOREVER); crc = calc_crc32((uint8_t *)item->data_ptr, item->data_size); hdr.magic = PARABAK_MAGIC; hdr.version = PARABAK_VERSION; hdr.data_len = item->data_size; hdr.data_crc = crc; get_timestamp(&hdr.time_stamp); hdr.state = 1; /* 已备份 */ memset(hdr.reserved, 0, sizeof(hdr.reserved)); if (write_header(part, &hdr) != RT_EOK) { LOG_E("backup: write header failed"); ret = -RT_ERROR; goto out; } if (write_data(part, (uint8_t *)item->data_ptr, item->data_size) != RT_EOK) { LOG_E("backup: write data failed"); ret = -RT_ERROR; goto out; } LOG_I("✅ Backup id %d (%s) SUCC, CRC=0x%08X, TIME=%02d/%02d/%02d %02d:%02d:%02d", id, item->partition_name, crc, hdr.time_stamp.tm_year, hdr.time_stamp.tm_mon, hdr.time_stamp.tm_mday, hdr.time_stamp.tm_hour, hdr.time_stamp.tm_min, hdr.time_stamp.tm_sec); Recode_e_code(e_bak_backup, id, "执行备份分区"); ret = RT_EOK; out: rt_mutex_release(parabak_lock); return ret; } int parabak_self_check(parabak_id_t id, parabak_header_t *hdr) { const struct fal_partition *part; parabak_header_t tmp_hdr; uint8_t *data_buf = RT_NULL; uint32_t crc; int ret = -1; if (ensure_initialized() != RT_EOK) { LOG_E("parabak not initialized"); return -1; } if (id >= PARABAK_ID_MAX) return -1; part = get_partition_by_id(id); if (part == RT_NULL) return -1; rt_mutex_take(parabak_lock, RT_WAITING_FOREVER); if (read_header(part, &tmp_hdr) != RT_EOK) { LOG_E("self_check: read header failed"); goto out; } if (hdr) *hdr = tmp_hdr; if (tmp_hdr.magic != PARABAK_MAGIC || tmp_hdr.version != PARABAK_VERSION) { LOG_D("self_check: invalid magic/version"); ret = 0; goto out; } if (tmp_hdr.data_len == 0 || tmp_hdr.data_len > SECTOR_SIZE) { LOG_W("self_check: invalid data len %d", tmp_hdr.data_len); ret = 0; goto out; } data_buf = rt_malloc(tmp_hdr.data_len); if (data_buf == RT_NULL) { LOG_E("self_check: out of memory"); goto out; } if (read_data(part, data_buf, tmp_hdr.data_len) != RT_EOK) { LOG_E("self_check: read data failed"); goto out; } crc = calc_crc32(data_buf, tmp_hdr.data_len); if (crc != tmp_hdr.data_crc) { LOG_W("self_check: CRC mismatch (stored=0x%08x, calc=0x%08x)", tmp_hdr.data_crc, crc); ret = 0; } else { ret = tmp_hdr.state; } out: if (data_buf) rt_free(data_buf); rt_mutex_release(parabak_lock); return ret; } int parabak_verify(parabak_id_t id) { const parabak_item_t *item; parabak_header_t hdr; int state; if (ensure_initialized() != RT_EOK) { LOG_E("parabak not initialized"); return -RT_ERROR; } if (id >= PARABAK_ID_MAX) return -RT_ERROR; item = ¶bak_items[id]; state = parabak_self_check(id, &hdr); if (state < 0) return -RT_ERROR; if (state == 0) { LOG_W("verify id %d: no valid backup", id); return -RT_ERROR; } uint32_t mem_crc = calc_crc32((uint8_t *)item->data_ptr, item->data_size); if (mem_crc != hdr.data_crc) { LOG_W("verify id %d: mem CRC mismatch (mem=0x%08x, backup=0x%08x)", id, mem_crc, hdr.data_crc); return -RT_ERROR; } LOG_I("✅ Verify OK: id=%d, %s=%d, CRC=0x%08X, TIME=%02d/%02d/%02d %02d:%02d:%02d", id, item->partition_name, hdr.state, hdr.data_crc, hdr.time_stamp.tm_year, hdr.time_stamp.tm_mon, hdr.time_stamp.tm_mday, hdr.time_stamp.tm_hour, hdr.time_stamp.tm_min, hdr.time_stamp.tm_sec); return RT_EOK; } int parabak_restore(parabak_id_t id) { const parabak_item_t *item; parabak_header_t hdr; uint8_t *data_buf; int state; int ret = -RT_ERROR; if (ensure_initialized() != RT_EOK) { LOG_E("parabak not initialized"); return -RT_ERROR; } if (id >= PARABAK_ID_MAX) return -RT_ERROR; item = ¶bak_items[id]; state = parabak_self_check(id, &hdr); if (state < 0) { LOG_E("restore id %d: self check failed", id); return -RT_ERROR; } if (state == 0) { LOG_W("restore id %d: no valid backup", id); return -RT_ERROR; } if (state == 2) { LOG_I("restore id %d: already restored once, skip", id); return RT_EOK; } data_buf = rt_malloc(hdr.data_len); if (data_buf == RT_NULL) { LOG_E("restore id %d: out of memory", id); return -RT_ERROR; } rt_mutex_take(parabak_lock, RT_WAITING_FOREVER); if (read_data(get_partition_by_id(id), data_buf, hdr.data_len) != RT_EOK) { LOG_E("restore id %d: read data failed", id); goto out; } uint32_t crc = calc_crc32(data_buf, hdr.data_len); if (crc != hdr.data_crc) { LOG_E("restore id %d: CRC mismatch after read", id); goto out; } /* 1. 恢复到内存 */ rt_memcpy(item->data_ptr, data_buf, hdr.data_len); /* 2. 写入 EasyFlash (env) */ int env_ret = ef_set_env_blob(item->env_key, item->data_ptr, item->data_size); if (env_ret != EF_NO_ERR) { LOG_E("restore id %d: write to env failed, ret=%d", id, env_ret); goto out; // 失败则退出,不更新头部状态 } LOG_I("restore id %d: env updated", id); /* 3. 更新头部状态为已还原 (2) */ hdr.state = 2; if (write_header(get_partition_by_id(id), &hdr) != RT_EOK) { LOG_W("restore id %d: update header state failed", id); // 即使头部更新失败,env已写入,内存已恢复,仍算成功,但状态未变下次可能再次还原 } LOG_I("restore id %d: memory and env restored", id); Recode_e_code(e_bak_restore, id, "执行还原分区"); ret = RT_EOK; out: rt_mutex_release(parabak_lock); rt_free(data_buf); return ret; } int parabak_get_header(parabak_id_t id, parabak_header_t *hdr) { const struct fal_partition *part; if (ensure_initialized() != RT_EOK) { LOG_E("parabak not initialized"); return -RT_ERROR; } if (id >= PARABAK_ID_MAX || hdr == RT_NULL) return -RT_ERROR; part = get_partition_by_id(id); if (part == RT_NULL) return -RT_ERROR; rt_mutex_take(parabak_lock, RT_WAITING_FOREVER); int ret = read_header(part, hdr); rt_mutex_release(parabak_lock); return ret; } /*============================================================================= * 自动校验功能(新增自动备份) *============================================================================*/ int parabak_auto_check(void) { int restored_id = -1; parabak_id_t id; int state; if (ensure_initialized() != RT_EOK) { LOG_E("auto_check: parabak not initialized"); return -1; } for (id = 0; id < PARABAK_ID_MAX; id++) { parabak_header_t hdr; state = parabak_self_check(id, &hdr); if (state < 0) { LOG_E("auto_check: self check failed for id %d", id); return -1; } if (state == 0) { // 未备份,执行一次备份 LOG_I("auto_check: no backup for id %d, performing initial backup", id); if (parabak_backup(id) != RT_EOK) { LOG_W("auto_check: initial backup failed for id %d", id); // 继续处理其他项 } } else if (state == 1) { // 已备份状态 if (parabak_verify(id) != RT_EOK) { LOG_W("auto_check: verify failed for id %d, try restore", id); if (parabak_restore(id) == RT_EOK) { if (restored_id == -1) { restored_id = id; // 记录第一个被还原的ID } } else { LOG_E("auto_check: restore failed for id %d", id); return -1; } } } else if (state == 2) { // 已还原状态,检查当前内存是否与备份一致 if (parabak_verify(id) == RT_EOK) { // 一致,说明系统稳定,将备份区状态置为已备份 LOG_I("auto_check: id %d already restored and consistent, set state to backup", id); const struct fal_partition *part = get_partition_by_id(id); if (part != RT_NULL) { rt_mutex_take(parabak_lock, RT_WAITING_FOREVER); parabak_header_t hdr_mod; if (read_header(part, &hdr_mod) == RT_EOK && hdr_mod.state == 2) { hdr_mod.state = 1; if (write_header(part, &hdr_mod) == RT_EOK) { LOG_I("auto_check: id %d state changed from 2 to 1", id); } else { LOG_W("auto_check: id %d state change failed", id); } } rt_mutex_release(parabak_lock); } } } } if (restored_id >= 0) { LOG_I("auto_check: some partitions restored, first id=%d", restored_id); return restored_id; } LOG_I("auto_check: all partitions OK"); return 0; } /** * @brief 测试指令 * */ /* 擦除所有备份分区(仅测试用) */ static void parabak_erase_all(int argc, char **argv) { parabak_id_t id; int count = 0; int ret; if (ensure_initialized() != RT_EOK) { LOG_E("parabak not initialized"); return; } LOG_W("!!! Erasing all backup partitions !!!"); for (id = 0; id < PARABAK_ID_MAX; id++) { const struct fal_partition *part = get_partition_by_id(id); if (part == RT_NULL) { LOG_E("partition not found for id %d", id); continue; } rt_mutex_take(parabak_lock, RT_WAITING_FOREVER); ret = fal_partition_erase(part, 0, PARABAK_PART_SIZE); rt_mutex_release(parabak_lock); if (ret < 0) { LOG_E("erase partition %s failed", part->name); } else { LOG_I("erased partition %s", part->name); count++; } } LOG_I("Erased %d partitions", count); } MSH_CMD_EXPORT(parabak_erase_all, Erase all backup partitions (for test only));