FTL复位丢失修复:journal epoch 锚定最新 checkpoint(根治重格式化循环)+ checkpoint 诊断
This commit is contained in:
+122
-3
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user