From 7a72aea4e59addf717ff48acaa68ad5c49c13d36 Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Sun, 20 Sep 2026 04:07:59 +0800 Subject: [PATCH] =?UTF-8?q?FTL=E5=A4=8D=E4=BD=8D=E4=B8=A2=E5=A4=B1?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=EF=BC=9Ajournal=20epoch=20=E9=94=9A=E5=AE=9A?= =?UTF-8?q?=E6=9C=80=E6=96=B0=20checkpoint=EF=BC=88=E6=A0=B9=E6=B2=BB?= =?UTF-8?q?=E9=87=8D=E6=A0=BC=E5=BC=8F=E5=8C=96=E5=BE=AA=E7=8E=AF=EF=BC=89?= =?UTF-8?q?+=20checkpoint=20=E8=AF=8A=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Drivers/BSP/GD5F2GQ5UE/nand_ftl.c | 73 +++++++++++++++++ Drivers/BSP/GD5F2GQ5UE/nand_ftl.h | 4 + Lib/dhara/journal.c | 125 +++++++++++++++++++++++++++++- Src/freertos.c | 5 ++ 4 files changed, 204 insertions(+), 3 deletions(-) diff --git a/Drivers/BSP/GD5F2GQ5UE/nand_ftl.c b/Drivers/BSP/GD5F2GQ5UE/nand_ftl.c index 9269d6bd..9d281bfd 100644 --- a/Drivers/BSP/GD5F2GQ5UE/nand_ftl.c +++ b/Drivers/BSP/GD5F2GQ5UE/nand_ftl.c @@ -401,3 +401,76 @@ int nand_ftl_format(void) { DBG_INFO("NAND FTL formatted, capacity: %lu pages", dhara_map_capacity(&s_map)); return GD5F_OK; } + +/* ======================== 诊断(跨复位日志持久性分析) ======================== */ + +/* 打印当前 journal/map 状态(epoch/head/tail/root/容量) */ +void nand_ftl_log_state(const char *tag) +{ + const struct dhara_journal *j = &s_map.journal; + + if (!s_initialized) { + return; + } + + DBG_INFO("FTL state %s: epoch=%u head=%lu tail=%lu root=%lu jsize=%lu map=%lu", + tag, (unsigned int)j->epoch, + (unsigned long)j->head, (unsigned long)j->tail, + (unsigned long)j->root, + (unsigned long)dhara_journal_size(j), + (unsigned long)dhara_map_size(&s_map)); +} + +/* 全扫每块首组 checkpoint 页(magic+epoch),打印连续区段分布, + * 用于识别 journal 环上的 epoch 分界与空洞 */ +void nand_ftl_scan_checks(void) +{ + uint32_t i; + uint8_t buf[8]; + uint32_t total = 0; + uint32_t run_start = 0; + uint32_t run_prev = 0; + uint32_t run_len = 0; + uint8_t run_epoch = 0; + uint8_t run_open = 0; + + if (!s_initialized) { + return; + } + + DBG_INFO("check scan start: blocks=%lu", (unsigned long)s_nand.num_blocks); + + for (i = 0; i < s_nand.num_blocks; i++) { + const uint32_t pg = ftl_phy_page((i << s_nand.log2_ppb) | + ((1u << s_map.journal.log2_ppc) - 1u)); + + if (gd5f_page_read(pg) != GD5F_OK) { + continue; + } + gd5f_read_from_cache(0, buf, sizeof(buf)); + if (buf[0] == 'D' && buf[1] == 'h' && buf[2] == 'a') { + const uint8_t ep = buf[3]; + + total++; + if (!run_open || (i != run_prev + 1) || (ep != run_epoch)) { + if (run_open) { + DBG_INFO("check run: blk %lu..%lu epoch=%u (%lu blocks)", + (unsigned long)run_start, (unsigned long)run_prev, + (unsigned int)run_epoch, (unsigned long)run_len); + } + run_start = i; + run_epoch = ep; + run_len = 0; + run_open = 1; + } + run_prev = i; + run_len++; + } + } + if (run_open) { + DBG_INFO("check run: blk %lu..%lu epoch=%u (%lu blocks)", + (unsigned long)run_start, (unsigned long)run_prev, + (unsigned int)run_epoch, (unsigned long)run_len); + } + DBG_INFO("check scan done: %lu checkpoint blocks", (unsigned long)total); +} diff --git a/Drivers/BSP/GD5F2GQ5UE/nand_ftl.h b/Drivers/BSP/GD5F2GQ5UE/nand_ftl.h index 183c07ae..a216e7ec 100644 --- a/Drivers/BSP/GD5F2GQ5UE/nand_ftl.h +++ b/Drivers/BSP/GD5F2GQ5UE/nand_ftl.h @@ -11,6 +11,10 @@ int nand_ftl_init(void); int nand_ftl_format(void); void nand_ftl_deinit(void); +/* 诊断接口:打印 journal 状态 / 全扫 checkpoint 分布(跨复位持久性分析) */ +void nand_ftl_log_state(const char *tag); +void nand_ftl_scan_checks(void); + #ifdef __cplusplus } #endif diff --git a/Lib/dhara/journal.c b/Lib/dhara/journal.c index 87743c39..7bfe2677 100644 --- a/Lib/dhara/journal.c +++ b/Lib/dhara/journal.c @@ -279,6 +279,99 @@ static dhara_block_t find_last_checkblock(struct dhara_journal *j, return first; } +/* Like find_last_checkblock() but without the epoch filter: find the + * last block holding a valid checkpoint of ANY epoch. Used to anchor + * the epoch on the newest surviving checkpoint (see + * dhara_journal_resume). + */ +static dhara_block_t find_last_checkblock_any(struct dhara_journal *j, + dhara_block_t first) +{ + dhara_block_t low = first; + dhara_block_t high = j->nand->num_blocks - 1; + + while (low <= high) { + const dhara_block_t mid = (low + high) >> 1; + dhara_block_t found; + + if (find_checkblock(j, mid, &found, NULL) < 0) { + if (!mid) + return first; + + high = mid - 1; + } else { + dhara_block_t nf; + + if (((found + 1) >= j->nand->num_blocks) || + (find_checkblock(j, found + 1, + &nf, NULL) < 0)) + return found; + + low = nf; + } + } + + return first; +} + +/* Re-read the first-group checkpoint header of the given block into + * j->page_buf. Returns 0 on success. */ +static int read_checkpage(struct dhara_journal *j, dhara_block_t blk, + dhara_error_t *err) +{ + const dhara_page_t p = (blk << j->nand->log2_ppb) | + ((1 << j->log2_ppc) - 1); + + if (dhara_nand_read(j->nand, p, 0, 1 << j->nand->log2_page_size, + j->page_buf, err) < 0 || + !hdr_has_magic(j->page_buf)) + return -1; + + return 0; +} + +/* First block whose checkpoint carries the current (newest) epoch. The + * epoch pattern along the ring is monotonic (one run per lifetime), so + * binary search applies; a checkpoint gap (e.g. a run of bad blocks) + * falls back to a forward scan. + */ +static dhara_block_t find_epoch_first(struct dhara_journal *j, + dhara_block_t first, + dhara_block_t last) +{ + dhara_block_t lo = first; + dhara_block_t hi = last; + + while (lo < hi) { + const dhara_block_t mid = (lo + hi) >> 1; + dhara_block_t cb; + dhara_error_t err; + + if (find_checkblock(j, mid, &cb, &err) < 0) { + /* Gap: forward-scan checkpoint by checkpoint. */ + dhara_block_t b = lo; + + while (b <= hi) { + dhara_error_t le; + + if (find_checkblock(j, b, &cb, &le) < 0) + return last; + if (hdr_get_epoch(j->page_buf) == j->epoch) + return cb; + b = cb + 1; + } + return last; + } + + if (hdr_get_epoch(j->page_buf) == j->epoch) + hi = cb; + else + lo = cb + 1; + } + + return lo; +} + /* Test whether a checkpoint group is in a state fit for reprogramming, * but allow for the fact that is_free() might not have any way of * distinguishing between an unprogrammed page, and a page programmed @@ -421,18 +514,44 @@ static int find_head(struct dhara_journal *j, dhara_page_t start, int dhara_journal_resume(struct dhara_journal *j, dhara_error_t *err) { - dhara_block_t first, last; + dhara_block_t first, last, last_any; dhara_page_t last_group; + uint8_t first_epoch; /* Find the first checkpoint-containing block */ if (find_checkblock(j, 0, &first, err) < 0) { reset_journal(j); return -1; } + first_epoch = hdr_get_epoch(j->page_buf); - /* Find the last checkpoint-containing block in this epoch */ + /* + * Anchor the epoch on the NEWEST surviving checkpoint, not the + * oldest. If the ring wrapped (epoch rolled) or the journal was + * abandoned mid-ring (chip swap, firmware change, long power + * off), the oldest checkpoint belongs to an older epoch; anchoring + * on it makes the epoch filter below reject every current-epoch + * checkpoint, silently discarding all writes of the current + * lifetime on the next resume. Measured 2026-09-20: this made a + * self-heal volume reformat vanish on every reset (infinite + * reformat loop, no data persistence). + */ + last_any = find_last_checkblock_any(j, first); + if (read_checkpage(j, last_any, err) < 0) { + reset_journal(j); + return -1; + } j->epoch = hdr_get_epoch(j->page_buf); - last = find_last_checkblock(j, first); + + if (first_epoch == j->epoch) { + /* Single-epoch ring: original code path, unchanged. */ + last = find_last_checkblock(j, first); + } else { + /* Multiple epochs: isolate the current epoch's window, + * so the epoch-filtered search below stays sound. */ + first = find_epoch_first(j, first, last_any); + last = find_last_checkblock(j, first); + } /* Find the last programmed checkpoint group in the block */ last_group = find_last_group(j, last); diff --git a/Src/freertos.c b/Src/freertos.c index 98d95d94..0735220c 100644 --- a/Src/freertos.c +++ b/Src/freertos.c @@ -33,6 +33,7 @@ #include "usart.h" #include "ff.h" #include "gd5f2gq5ue.h" +#include "nand_ftl.h" #include "lftpd.h" #include "../test/ch395f_test_task.h" #include "../test/ch395f_test.h" /* ENABLE_NET_LAYER_TESTS:netTask 条件创建 */ @@ -262,6 +263,7 @@ void StartDefaultTask(void *argument) FS_VOL_PROBE(hfr); if (hfr != FR_OK) { FS_VOL_PROBE(hfr); /* 两击确认 */ + nand_ftl_scan_checks(); /* 诊断:环上 checkpoint 的 epoch 分布 */ } if (hfr != FR_OK) { if (hfr == FR_DENIED || hfr == FR_INVALID_OBJECT || @@ -278,6 +280,8 @@ void StartDefaultTask(void *argument) fr = f_mount(&default_fatfs, "", 1); } } + nand_ftl_log_state("[after-mkfs]"); + nand_ftl_log_state("[after-mkfs]"); if (fr == FR_OK) { DBG_INFO("[FS] volume reformatted OK"); } else { @@ -295,6 +299,7 @@ void StartDefaultTask(void *argument) #undef FS_VOL_PROBE if (fr == FR_OK) { DBG_INFO("[FS] FATFS mounted"); + nand_ftl_log_state("[probe-ok]"); /* 日志引擎挂载后处理:建 /log 目录 + 写"上电/重启"分类记录(唯一写者) */ syslog_mcu_mounted(); /* 加载网络配置进 RAM 副本(配置引擎属主 = 本任务;首启含旧档迁移;