From e83b7d6dc4eace8eeb667bcdcd734d8f7dabbc05 Mon Sep 17 00:00:00 2001 From: Marcin Bukat Date: Thu, 1 Oct 2026 09:28:08 +0200 Subject: [PATCH] rk27xx: add a finder for the NAND's FTL scheme Many rk27xx targets have NAND whose format nobody has examined. The finder reads ID block 1 and the first page of the first 512 blocks and says which FTL formatted them: Scheme A by its remap-log blocks, Scheme B by its bad-block table and data headers, another Scheme B generation by other 0xFxxx tags. The later ID block layout ('RK27' at 0x0a) records the FTL area's BCH strength at 0x1ed - 8 on the HM-601, 14 on the Archos Vision 28 - and the scan reads in that mode. It is read-only, shown in the debug menu as "View FTL scheme", and built for targets whose NAND is not storage - none yet. Run on dumps of an HM-601 it reports Scheme B, a Samsung YP-CP3 Scheme A, and an Archos Vision 28 the other Scheme B generation. Co-Authored-By: Claude Opus 5.5 Change-Id: I0528f9d2a6996089b6488a77b43942f6fd1f2c16 --- apps/debug_menu.c | 56 +++++ firmware/SOURCES | 5 + firmware/target/arm/rk27xx/ftl-probe-rk27xx.c | 219 ++++++++++++++++++ firmware/target/arm/rk27xx/ftl-probe-rk27xx.h | 83 +++++++ 4 files changed, 363 insertions(+) create mode 100644 firmware/target/arm/rk27xx/ftl-probe-rk27xx.c create mode 100644 firmware/target/arm/rk27xx/ftl-probe-rk27xx.h diff --git a/apps/debug_menu.c b/apps/debug_menu.c index 95e744f160..db8203643f 100644 --- a/apps/debug_menu.c +++ b/apps/debug_menu.c @@ -2857,6 +2857,58 @@ static bool view_ram_info(void) } #endif +#if (CONFIG_PLATFORM & PLATFORM_NATIVE) && (CONFIG_NAND == NAND_RK27XX) \ + && !(CONFIG_STORAGE & STORAGE_NAND) +#include "ftl-probe-rk27xx.h" + +/* The target does not know its NAND's FTL scheme yet: show what the + * finder sees, for the user to report */ +static bool dbg_ftl_scheme(void) +{ + struct ftl_probe p; + struct simplelist_info info; + + ftl_probe(&p); + simplelist_info_init(&info, "FTL scheme", 0, NULL); + simplelist_reset_lines(); + simplelist_addline("Scheme: %s", ftl_probe_scheme_name(p.scheme)); + if (p.flash_error) + { + simplelist_addline("No usable NAND (%d)", p.flash_error); + } + else + { + simplelist_addline("Chip: %lu blocks", (unsigned long)p.blocks); + simplelist_addline("%u planes, %u/%u sec", p.planes, p.sec_per_page, + p.sec_per_block); + if (p.idb_boot_blocks) + { + simplelist_addline("IDB: %u blk %u+%u MB", p.idb_boot_blocks, + p.idb_sys_mb, p.idb_data_mb); + if (p.idb_rk27) + simplelist_addline("IDB: RK27, ECC t=%u", p.idb_ecc_t); + else + simplelist_addline("IDB: early layout"); + } + else + simplelist_addline("IDB: none"); + simplelist_addline("Scanned %u blocks, t=%u", p.scanned, p.scan_ecc_t); + simplelist_addline("Erased %u, bad ECC %u", p.erased, p.unreadable); + simplelist_addline("A: %u logs, %u used", p.a_logs, p.a_blocks); + simplelist_addline("B: %u tables at %u", p.b_tables, + p.b_tables ? p.first_b_table : 0); + simplelist_addline("B: %u cache, %u data", p.b_cache, p.b_data); + if (p.b_other) + simplelist_addline("B: %u other, %04x", p.b_other, p.first_b_other); + if (p.other) + simplelist_addline("Other: %u, %u %02x%02x%02x", p.other, + p.first_other, p.first_other_meta[0], + p.first_other_meta[1], p.first_other_meta[2]); + } + return simplelist_show_list(&info); +} +#endif + /****** The menu *********/ static const struct { unsigned char *desc; /* string or ID */ @@ -2909,6 +2961,10 @@ static const struct { #if ((CONFIG_PLATFORM & PLATFORM_NATIVE) || defined(SONY_NWZ_LINUX) || defined(HIBY_LINUX) || defined(FIIO_M3K_LINUX)) && !defined(SIMULATOR) { "View HW info", dbg_hw_info }, #endif +#if (CONFIG_PLATFORM & PLATFORM_NATIVE) && (CONFIG_NAND == NAND_RK27XX) \ + && !(CONFIG_STORAGE & STORAGE_NAND) + { "View FTL scheme", dbg_ftl_scheme }, +#endif #if (CONFIG_PLATFORM & PLATFORM_NATIVE) { "View partitions", dbg_partitions }, #endif diff --git a/firmware/SOURCES b/firmware/SOURCES index 0bfa9f45cf..46966bb507 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -1714,6 +1714,11 @@ target/arm/rk27xx/ftl-rk27xx.c target/arm/rk27xx/ftl-scheme-a.c target/arm/rk27xx/flash-rk27xx.c target/arm/rk27xx/nand-rk27xx.c +#if (CONFIG_NAND == NAND_RK27XX) && !(CONFIG_STORAGE & STORAGE_NAND) \ + && !defined(BOOTLOADER) +/* no FTL scheme configured: the finder (debug menu) */ +target/arm/rk27xx/ftl-probe-rk27xx.c +#endif target/arm/rk27xx/usb-rk27xx.c target/arm/rk27xx/lcdif-rk27xx.c target/arm/rk27xx/rkw-loader.c diff --git a/firmware/target/arm/rk27xx/ftl-probe-rk27xx.c b/firmware/target/arm/rk27xx/ftl-probe-rk27xx.c new file mode 100644 index 0000000000..3b04d08f65 --- /dev/null +++ b/firmware/target/arm/rk27xx/ftl-probe-rk27xx.c @@ -0,0 +1,219 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2026 by Marcin Bukat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +/* The FTL scheme finder - see ftl-probe-rk27xx.h. + * + * The two formats tell themselves apart in the metadata of a block's first + * sector (flash-rk27xx.h): Scheme A marks its remap-log blocks with type + * 0x52 in byte 2 (ftl-scheme-a.c), Scheme B keeps a 0xfxxx tag in bytes 0-1 + * - 0xf000 for its bad-block table near the start of the chip, 0xf100 and + * 0xf200 for its cache and data blocks (ftl-scheme-b.c). Every zone of + * Scheme A has two log blocks, the first zone within its first 256 blocks; + * Scheme B's table lies within 50 blocks of the boot area. So the first + * SCAN_BLOCKS blocks decide. + * + * They are read in the BCH mode the FTL's area uses, which need not be the + * boot area's t=8: the Archos Vision writes its FTL area with t=14. ID + * block 1 of the later generation - 'RK27' at 0x0a - records it at 0x1ed + * (8 on the HM-601, 14 on the Archos); the earlier generation's ID block + * has no such field, and its devices are t=8 throughout. */ + +#include +#include + +#include "config.h" +#include "nand-target.h" +#include "flash-rk27xx.h" +#include "ftl-probe-rk27xx.h" + +#define SCAN_BLOCKS 512 + +/* ID blocks: every IDB_STRIDE-th raw sector of the boot area, metadata type + * IDB_TYPE; ID block 1 is the sector after (ftl-rk27xx.c) */ +#define IDB_STRIDE 512 +#define IDB_POSITIONS 50 +#define IDB_TYPE 0x69 +#define IDB1_MAGIC 0x0a /* 'RK27' in the later generation's ... */ +#define IDB1_ECC_T 0x1ed /* ... which records the BCH t here */ +#define DEFAULT_ECC_T 8 + +#define A_LOG_TYPE 0x52 +#define B_TAG_TABLE 0xf000 +#define B_TAG_CACHE 0xf100 +#define B_TAG_DATA 0xf200 + +static void probe_idb(struct ftl_probe *p) +{ + uint8_t data[FLASH_SECTOR_SIZE], meta[FLASH_META_SIZE]; + uint32_t pos; + bool found = false; + + for (pos = 0; pos < IDB_POSITIONS && !found; pos++) + { + uint32_t raw = pos * IDB_STRIDE; + + if (flash_read_raw(raw, data, meta) == 0 && meta[2] == IDB_TYPE && + flash_read_raw(raw + 1, data, meta) == 0) + { + p->idb_boot_blocks = (uint16_t)(data[0] | data[1] << 8); + p->idb_sys_mb = (uint16_t)(data[2] | data[3] << 8); + p->idb_data_mb = (uint16_t)(data[4] | data[5] << 8); + p->idb_rk27 = memcmp(data + IDB1_MAGIC, "RK27", 4) == 0; + if (p->idb_rk27) + { + p->idb_ecc_t = data[IDB1_ECC_T]; + } + found = true; + } + } +} + +static void probe_block(struct ftl_probe *p, uint32_t blk) +{ + uint8_t meta[FLASH_META_SIZE]; + uint16_t tag; + + if (flash_read(blk * p->sec_per_block, NULL, meta, 1) != 0) + { + p->unreadable++; + } + else + { + tag = (uint16_t)(meta[0] | meta[1] << 8); + + /* tag 0xffff: erased, or a block only its byte 2 marks - an ID + * block, type 0x69 */ + if (tag == 0xffff) + { + p->erased++; + } + else if (meta[0] == 0xff && meta[1] == 0x00 && meta[2] == A_LOG_TYPE) + { + p->a_logs++; + } + else if (meta[1] == 0x00) + { + /* Scheme A's flash layer zeroes byte 1 of every page */ + p->a_blocks++; + } + else if (tag == B_TAG_TABLE) + { + if (p->b_tables++ == 0) + { + p->first_b_table = (uint16_t)blk; + } + } + else if ((tag & 0xff00) == B_TAG_CACHE) + { + p->b_cache++; + } + else if ((tag & 0xff00) == B_TAG_DATA) + { + p->b_data++; + } + else if ((tag & 0xf000) == 0xf000) + { + if (p->b_other++ == 0) + { + p->first_b_other = tag; + } + } + else if (p->other++ == 0) + { + p->first_other = (uint16_t)blk; + memcpy(p->first_other_meta, meta, sizeof(p->first_other_meta)); + } + } +} + +void ftl_probe(struct ftl_probe *p) +{ + const struct flash_geometry *geo; + uint32_t blk; + + memset(p, 0, sizeof(*p)); + p->first_b_table = 0xffff; + + flash_init(); + p->flash_error = flash_layer_init(); + + if (p->flash_error == 0) + { + geo = flash_get_geometry(); + p->planes = geo->planes; + p->sec_per_page = geo->sec_per_page; + p->sec_per_block = geo->sec_per_block; + p->blocks = geo->total_blocks; + + probe_idb(p); + + /* a value the controller has no mode for is reported, and t=8 used */ + p->scan_ecc_t = DEFAULT_ECC_T; + if (p->idb_rk27 && flash_set_ecc(p->idb_ecc_t) == 0) + { + p->scan_ecc_t = p->idb_ecc_t; + } + + for (blk = 0; blk < SCAN_BLOCKS && blk < p->blocks; blk++) + { + probe_block(p, blk); + } + p->scanned = (uint16_t)blk; + flash_set_ecc(DEFAULT_ECC_T); + + if (p->b_tables > 0 && p->b_data > 0) + { + p->scheme = p->b_other > 0 ? FTL_PROBE_SCHEME_B_OTHER + : FTL_PROBE_SCHEME_B; + } + else if (p->a_logs >= 2 && p->b_tables == 0) + { + p->scheme = FTL_PROBE_SCHEME_A; + } + else if (p->unreadable > p->scanned / 2) + { + p->scheme = FTL_PROBE_UNREADABLE; + } + } +} + +const char *ftl_probe_scheme_name(enum ftl_probe_scheme scheme) +{ + const char *name = "unknown"; + + switch (scheme) + { + case FTL_PROBE_SCHEME_A: + name = "A"; + break; + case FTL_PROBE_SCHEME_B: + name = "B"; + break; + case FTL_PROBE_UNREADABLE: + name = "unknown, ECC fails"; + break; + case FTL_PROBE_SCHEME_B_OTHER: + name = "B, other generation"; + break; + default: + break; + } + return name; +} diff --git a/firmware/target/arm/rk27xx/ftl-probe-rk27xx.h b/firmware/target/arm/rk27xx/ftl-probe-rk27xx.h new file mode 100644 index 0000000000..cabd75a310 --- /dev/null +++ b/firmware/target/arm/rk27xx/ftl-probe-rk27xx.h @@ -0,0 +1,83 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2026 by Marcin Bukat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +/* Which flash translation layer formatted an rk27xx target's NAND - for + * targets whose config does not define CONFIG_RK27XX_FTL yet. Read-only: + * it looks at the first page of the first blocks and nothing else. */ + +#ifndef __FTL_PROBE_RK27XX_H__ +#define __FTL_PROBE_RK27XX_H__ + +#include +#include + +enum ftl_probe_scheme +{ + FTL_PROBE_UNKNOWN = 0, + FTL_PROBE_SCHEME_A, /* remap-log blocks: ftl-scheme-a.c */ + FTL_PROBE_SCHEME_B, /* bad-block table and data headers: + * ftl-scheme-b.c */ + FTL_PROBE_SCHEME_B_OTHER, /* Scheme B tags of another generation */ + FTL_PROBE_UNREADABLE, /* most blocks fail ECC in the mode used */ +}; + +struct ftl_probe +{ + enum ftl_probe_scheme scheme; + int flash_error; /* flash_layer_init() result; 0 = chip found */ + + /* the chip */ + uint8_t planes; + uint8_t sec_per_page; /* both planes */ + uint16_t sec_per_block; + uint32_t blocks; + + /* ID block 1, 0 if none found */ + uint16_t idb_boot_blocks; /* raw blocks */ + uint16_t idb_sys_mb; + uint16_t idb_data_mb; + bool idb_rk27; /* the later layout, with 'RK27' at 0x0a ... */ + uint8_t idb_ecc_t; /* ... and the FTL area's BCH t at 0x1ed */ + + uint8_t scan_ecc_t; /* the BCH t the blocks were read with */ + + /* what page 0 of the blocks scanned holds */ + uint16_t scanned; + uint16_t unreadable; + uint16_t erased; + uint16_t a_logs; /* Scheme A remap-log blocks */ + uint16_t a_blocks; /* other blocks Scheme A has programmed */ + uint16_t b_tables; /* Scheme B: 0xf000 bad-block table */ + uint16_t b_cache; /* 0xf1xx */ + uint16_t b_data; /* 0xf2xx */ + uint16_t b_other; /* other 0xfxxx tags */ + uint16_t other; /* none of these */ + uint16_t first_b_table; /* block of the first 0xf000; 0xffff = none */ + uint16_t first_b_other; /* the first other 0xfxxx tag; 0 = none */ + uint16_t first_other; /* first block of none of these ... */ + uint8_t first_other_meta[3]; /* ... and its metadata */ +}; + +/* Run the probe; takes a fraction of a second */ +void ftl_probe(struct ftl_probe *p); + +const char *ftl_probe_scheme_name(enum ftl_probe_scheme scheme); + +#endif /* __FTL_PROBE_RK27XX_H__ */