From 6bf3eaff4161f9fe071b1356823bb8f6fba29c19 Mon Sep 17 00:00:00 2001 From: Marcin Bukat Date: Thu, 1 Oct 2026 09:26:56 +0200 Subject: [PATCH] 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 Change-Id: I49b2c24d812284af2c50d776f0942b5b1b989c98 --- firmware/target/arm/rk27xx/flash-rk27xx.c | 56 +++++++++++++++++++++-- firmware/target/arm/rk27xx/flash-rk27xx.h | 18 ++++++-- 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/firmware/target/arm/rk27xx/flash-rk27xx.c b/firmware/target/arm/rk27xx/flash-rk27xx.c index c419efcc9f..355d9268ea 100644 --- a/firmware/target/arm/rk27xx/flash-rk27xx.c +++ b/firmware/target/arm/rk27xx/flash-rk27xx.c @@ -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); } - spare[META_PROGRAMMED] = 0x00; + 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); +} diff --git a/firmware/target/arm/rk27xx/flash-rk27xx.h b/firmware/target/arm/rk27xx/flash-rk27xx.h index 952d537ffc..fe6074069f 100644 --- a/firmware/target/arm/rk27xx/flash-rk27xx.h +++ b/firmware/target/arm/rk27xx/flash-rk27xx.h @@ -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);