rk27xx: let an FTL keep its own data in metadata byte 1

The flash layer writes 0x00 into metadata byte 1 of every page it
programs, which is how Scheme A tells a programmed page from an erased
one. Scheme B keeps a 16-bit field in bytes 0-1 of every sector - its
block tags, versions and block numbers - so it needs the byte as
written.

Add flash_set_meta_passthrough() to turn the forcing off, and
flash_copy_meta(), a copy that either keeps each sector's own metadata
or programs a page of it given by the caller: Scheme B's copy stamps
one header on every page it moves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I49b2c24d812284af2c50d776f0942b5b1b989c98
This commit is contained in:
Marcin Bukat 2026-10-01 09:26:56 +02:00
parent 0db035d231
commit 6bf3eaff41
2 changed files with 67 additions and 7 deletions

View file

@ -76,6 +76,7 @@
static struct flash_geometry geo;
static bool ready;
static bool writable;
static bool meta_passthrough;
static uint32_t boot_area = UINT32_MAX;
static struct flash_stats stats;
@ -129,6 +130,11 @@ void flash_set_boot_area(uint32_t sectors)
boot_area = sectors;
}
void flash_set_meta_passthrough(bool on)
{
meta_passthrough = on;
}
void flash_get_stats(struct flash_stats *out)
{
*out = stats;
@ -393,7 +399,10 @@ static int prog_raw_run(uint32_t row, uint32_t first, uint32_t n,
{
memcpy(spare, meta + i * FLASH_META_SIZE, FLASH_META_SIZE);
}
if (!meta_passthrough)
{
spare[META_PROGRAMMED] = 0x00;
}
/* a slot is reused every four sectors: its last transfer must be
* done */
@ -547,14 +556,24 @@ int flash_erase(uint32_t sec)
return ret;
}
/* What a copy programs as the destination's metadata */
enum copy_meta
{
COPY_META_FRESH, /* as flash_program() with meta NULL */
COPY_META_KEEP, /* each sector's own */
COPY_META_PAGE, /* the caller's, by position in the page */
};
/* Copy through the ECC engine, never with the chip's internal data move: on
* this MLC part moved pages accumulate bit errors, and the data area's
* t=8 is already below the chip's 12-bit minimum. A source sector that
* fails ECC is copied as read, and counted. */
int flash_copy(uint32_t src, uint32_t dst, unsigned n)
static int copy_sectors(uint32_t src, uint32_t dst, unsigned n,
enum copy_meta how, const uint8_t *page_meta)
{
static uint8_t buf[FLASH_MAX_SEC_PER_PAGE * FLASH_SECTOR_SIZE]
__attribute__((aligned(4)));
static uint8_t meta[FLASH_MAX_SEC_PER_PAGE * FLASH_META_SIZE];
uint32_t k = 0;
int ret = 0;
@ -599,14 +618,30 @@ int flash_copy(uint32_t src, uint32_t dst, unsigned n)
}
if (read_raw_run(raw / geo.sec_per_page_raw, first, run,
buf + (size_t)i * FLASH_SECTOR_SIZE, NULL))
buf + (size_t)i * FLASH_SECTOR_SIZE,
how == COPY_META_KEEP ?
meta + (size_t)i * FLASH_META_SIZE : NULL))
{
stats.copy_uncorrectable++;
}
i += run;
}
if (ret == 0 && flash_program(dst + k, buf, NULL, len))
if (how == COPY_META_PAGE)
{
for (i = 0; i < len; i++)
{
uint32_t at = (dst + k + i) % geo.sec_per_page;
memcpy(meta + i * FLASH_META_SIZE,
page_meta + at * FLASH_META_SIZE,
FLASH_META_SIZE);
}
}
if (ret == 0
&& flash_program(dst + k, buf,
how == COPY_META_FRESH ? NULL : meta, len))
{
ret = 1;
}
@ -615,3 +650,16 @@ int flash_copy(uint32_t src, uint32_t dst, unsigned n)
}
return ret;
}
int flash_copy(uint32_t src, uint32_t dst, unsigned n)
{
return copy_sectors(src, dst, n, COPY_META_FRESH, NULL);
}
int flash_copy_meta(uint32_t src, uint32_t dst, unsigned n,
const void *page_meta)
{
return copy_sectors(src, dst, n,
page_meta ? COPY_META_PAGE : COPY_META_KEEP,
page_meta);
}

View file

@ -33,8 +33,10 @@
* METADATA: every sector carries 16 spare bytes. The last 13 are the BCH
* code and belong to the hardware; the first three are the FTL's, and are
* what the meta arguments below carry - FLASH_META_SIZE bytes per sector.
* Byte 1 is special: this layer writes 0x00 there on every program, so that
* the FTL can tell a programmed page from an erased one whatever it wrote.
* Byte 1 is special: by default this layer writes 0x00 there on every
* program, so that the FTL can tell a programmed page from an erased one
* whatever it wrote. An FTL that keeps its own data in byte 1 turns that off
* with flash_set_meta_passthrough().
*
* Only the first chip is supported; every device the FTL has been validated
* on has one. */
@ -86,7 +88,8 @@ const struct flash_geometry *flash_get_geometry(void);
int flash_read(uint32_t sec, void *data, void *meta, unsigned n);
/* Program n sectors from sec, which must be erased. data NULL programs
* 0xff; meta NULL programs {0xff, 0x00, 0xff}. Returns 0 or 1 on failure. */
* 0xff; meta NULL programs {0xff, 0x00, 0xff} - {0xff, 0xff, 0xff} with
* passthrough on. Returns 0 or 1 on failure. */
int flash_program(uint32_t sec, const void *data, const void *meta, unsigned n);
/* Program the whole page (every plane) that contains sec. */
@ -100,6 +103,15 @@ int flash_erase(uint32_t sec);
* gets fresh metadata {0xff, 0x00, 0xff}. */
int flash_copy(uint32_t src, uint32_t dst, unsigned n);
/* As flash_copy(), but with chosen metadata: page_meta NULL keeps each
* sector's own; otherwise sector k of every destination page is programmed
* with page_meta[k] (sec_per_page * FLASH_META_SIZE bytes). */
int flash_copy_meta(uint32_t src, uint32_t dst, unsigned n,
const void *page_meta);
/* Program metadata byte 1 as given rather than 0x00. */
void flash_set_meta_passthrough(bool on);
/* Read one PHYSICAL sector, bypassing the super-block view: for the boot
* area, which the boot ROM addresses physically. */
int flash_read_raw(uint32_t raw_sec, void *data, void *meta);