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 <noreply@anthropic.com>
Change-Id: I0528f9d2a6996089b6488a77b43942f6fd1f2c16
This commit is contained in:
Marcin Bukat 2026-10-01 09:28:08 +02:00
parent 16492de569
commit e83b7d6dc4
4 changed files with 363 additions and 0 deletions

View file

@ -2857,6 +2857,58 @@ static bool view_ram_info(void)
} }
#endif #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 *********/ /****** The menu *********/
static const struct { static const struct {
unsigned char *desc; /* string or ID */ 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) #if ((CONFIG_PLATFORM & PLATFORM_NATIVE) || defined(SONY_NWZ_LINUX) || defined(HIBY_LINUX) || defined(FIIO_M3K_LINUX)) && !defined(SIMULATOR)
{ "View HW info", dbg_hw_info }, { "View HW info", dbg_hw_info },
#endif #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) #if (CONFIG_PLATFORM & PLATFORM_NATIVE)
{ "View partitions", dbg_partitions }, { "View partitions", dbg_partitions },
#endif #endif

View file

@ -1714,6 +1714,11 @@ target/arm/rk27xx/ftl-rk27xx.c
target/arm/rk27xx/ftl-scheme-a.c target/arm/rk27xx/ftl-scheme-a.c
target/arm/rk27xx/flash-rk27xx.c target/arm/rk27xx/flash-rk27xx.c
target/arm/rk27xx/nand-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/usb-rk27xx.c
target/arm/rk27xx/lcdif-rk27xx.c target/arm/rk27xx/lcdif-rk27xx.c
target/arm/rk27xx/rkw-loader.c target/arm/rk27xx/rkw-loader.c

View file

@ -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 <stdbool.h>
#include <string.h>
#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;
}

View file

@ -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 <stdbool.h>
#include <stdint.h>
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__ */