mirror of
https://github.com/Rockbox/rockbox.git
synced 2026-10-10 08:03:04 -04:00
rk27xx: add the Scheme A FTL
ftl-rk27xx.c has been four empty stubs since 2010. Fill them in with
the flash translation layer the rk2705/rk2706 original firmware uses,
called Scheme A here to tell it from the log-structured layout of later
firmware. It reads and writes that format exactly as the original
firmware does, so a device keeps working with its original firmware
after Rockbox has written to it.
- ftl-scheme-a.{c,h}: the FTL. The file opens with a description of
the on-flash format and how the FTL works: the SYS and USER volumes,
super-blocks and zones, the zone table, the remap log and its mirror,
the exchange record and the write protocol, power-loss recovery, bad
blocks. Oddities of the original firmware kept for compatibility are
marked where they are.
- Parameters that differ between firmware builds - the zone reserve
base, the system zone offset, the format flag - are recovered from
the media at mount and checked against its structure; a mount that
cannot confirm them is read-only. A mount that would have to repair
the remap log while not allowed to write fails rather than serve
wrong data.
- ftl-rk27xx.c: the storage glue. It finds the boot area's ID block,
which records where SYS ends, and mounts the FTL.
- ata-nand-rk27xx.c: a drive per volume. SYS holds the original
firmware - on a Rockbox device including the BASE.RKW that chainloads
the bootloader - and nothing of the user's, so it is a drive only when
the target defines HAVE_RK27XX_NAND_SYS. Capacity comes from the FTL's
tables, not from raw block geometry.
- config.h: HAVE_STORAGE_FLUSH for the rk27xx NAND. The FTL holds up to
three part-written pages in RAM; storage_flush() commits them at
shutdown and ROLO.
Writing is opt-in: without FTL_ALLOW_WRITE the FTL mounts read-only and
never writes the flash, not even a repair the mount could make.
Two bugs of the original firmware are not reproduced. A write starting
before a page held part-written in RAM and running through it left two
buffers holding that page, and the older one was later programmed over
the newer data; such a write now flushes the held page first. And its
bad-block marker took two of its three metadata bytes from the stack,
which can make a retired block look like a remap-log block; the marker
is now written in full.
Tested in a host simulator on NAND images of a Samsung YP-CP3 and a
generic rk2705, against the original firmware's FTL object run under
qemu-arm: identical traces of every read, program and erase, with a
hash of the data each program writes, over mounting and reading, random
writes with every sector verified, a power cut at every flash operation
of a write, and a program or erase failure at every one.
On a generic rk2705: the read-only mount reports the layout and
capacities the original firmware does and every file's MD5 matches; a
write test passes 8192/8192 across a remount and a power cycle.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I573d944389cefb456ecf38c6c7b91bebfe0fe377
This commit is contained in:
parent
f428c491d1
commit
a0c1085f3f
7 changed files with 2460 additions and 45 deletions
|
|
@ -1711,6 +1711,7 @@ target/arm/rk27xx/backlight-rk27xx.c
|
|||
target/arm/rk27xx/adc-rk27xx.c
|
||||
target/arm/rk27xx/sd-rk27xx.c
|
||||
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
|
||||
target/arm/rk27xx/usb-rk27xx.c
|
||||
|
|
|
|||
|
|
@ -893,6 +893,14 @@ Lyre prototype 1 */
|
|||
|
||||
/* Storage related config handling */
|
||||
|
||||
/* The rk27xx NAND's flash translation layer holds part-written pages in RAM
|
||||
* (ftl-scheme-a.c) until a later write completes them; storage_flush()
|
||||
* commits them at shutdown, ROLO and wherever else it is called. */
|
||||
#if (CONFIG_STORAGE & STORAGE_NAND) && (CONFIG_NAND == NAND_RK27XX) \
|
||||
&& !defined(HAVE_STORAGE_FLUSH)
|
||||
#define HAVE_STORAGE_FLUSH
|
||||
#endif
|
||||
|
||||
#if (CONFIG_STORAGE & (CONFIG_STORAGE - 1)) != 0
|
||||
/* Multiple storage drivers */
|
||||
#define CONFIG_STORAGE_MULTI
|
||||
|
|
|
|||
|
|
@ -28,26 +28,29 @@
|
|||
#include "ftl-target.h"
|
||||
#include "nand-target.h"
|
||||
|
||||
uint32_t ftl_banks;
|
||||
const struct nand_device_info_type* ftl_nand_type;
|
||||
/* This file provides only STUBS for now */
|
||||
|
||||
/** static, private data **/
|
||||
static bool initialized = false;
|
||||
|
||||
/* The NAND is FTL_NUM_DRIVES drives - USER, and SYS when the target exposes
|
||||
* it. storage.c hands us a drive index relative to our first, which is the
|
||||
* FTL_DRIVE_* number. */
|
||||
#ifdef HAVE_MULTIDRIVE
|
||||
#define NAND_DRIVE(d) (d)
|
||||
#else
|
||||
#define NAND_DRIVE(d) FTL_DRIVE_USER
|
||||
#endif
|
||||
|
||||
/* API Functions */
|
||||
int nand_read_sectors(IF_MD(int drive,) sector_t start, int incount,
|
||||
void* inbuf)
|
||||
{
|
||||
(void)drive;
|
||||
return ftl_read(start, incount, inbuf);
|
||||
return ftl_read(NAND_DRIVE(IF_MD_DRV(drive)), start, incount, inbuf);
|
||||
}
|
||||
|
||||
int nand_write_sectors(IF_MD(int drive,) sector_t start, int count,
|
||||
const void* outbuf)
|
||||
{
|
||||
(void)drive;
|
||||
return ftl_write(start, count, outbuf);
|
||||
return ftl_write(NAND_DRIVE(IF_MD_DRV(drive)), start, count, outbuf);
|
||||
}
|
||||
|
||||
void nand_spindown(int seconds)
|
||||
|
|
@ -72,13 +75,20 @@ void nand_enable(bool on)
|
|||
|
||||
void nand_get_info(IF_MD(int drive,) struct storage_info *info)
|
||||
{
|
||||
(void)drive;
|
||||
uint32_t ppb = ftl_banks * (*ftl_nand_type).pagesperblock;
|
||||
int d = NAND_DRIVE(IF_MD_DRV(drive));
|
||||
|
||||
/* Capacity comes from the FTL's own tables, not from raw block
|
||||
* geometry: Scheme A's usable size depends on the per-zone valid-block
|
||||
* counts and on the system/user split recorded in ID block 1. */
|
||||
(*info).sector_size = SECTOR_SIZE;
|
||||
(*info).num_sectors = (*ftl_nand_type).userblocks * ppb;
|
||||
(*info).vendor = "";
|
||||
(*info).product = "";
|
||||
(*info).revision = "";
|
||||
(*info).num_sectors = ftl_get_sectors(d);
|
||||
(*info).vendor = "Rockchip";
|
||||
#ifdef HAVE_RK27XX_NAND_SYS
|
||||
(*info).product = (d == FTL_DRIVE_SYS) ? "NAND SYS" : "NAND USER";
|
||||
#else
|
||||
(*info).product = "NAND USER";
|
||||
#endif
|
||||
(*info).revision = "1.0";
|
||||
}
|
||||
|
||||
long nand_last_disk_activity(void)
|
||||
|
|
@ -109,7 +119,11 @@ int nand_num_drives(int first_drive)
|
|||
/* We don't care which logical drive number(s) we have been assigned */
|
||||
(void)first_drive;
|
||||
|
||||
#ifdef HAVE_MULTIDRIVE
|
||||
return FTL_NUM_DRIVES;
|
||||
#else
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
* \/ \/ \/ \/ \/
|
||||
*
|
||||
* Copyright (C) 2010 by Bertrik Sikken
|
||||
* 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
|
||||
|
|
@ -17,39 +18,190 @@
|
|||
* KIND, either express or implied.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include "config.h"
|
||||
#include "ftl-target.h"
|
||||
|
||||
/* this file provides empty STUBS for now */
|
||||
/* Rockbox storage on the rk27xx Scheme A FTL (ftl-scheme-a.c).
|
||||
*
|
||||
* Writing is opt-in: a build without FTL_ALLOW_WRITE mounts read-only and
|
||||
* never writes the flash, not even the repairs a mount can make. */
|
||||
|
||||
#include "config.h"
|
||||
#include "system.h"
|
||||
#include "ftl-target.h"
|
||||
#include "nand-target.h"
|
||||
#include "flash-rk27xx.h"
|
||||
#include "ftl-scheme-a.h"
|
||||
|
||||
/* The boot ROM looks for ID blocks at every 512th raw sector of the boot
|
||||
* area, up to 50 positions, by metadata type 0x69. */
|
||||
#define IDB_STRIDE 512
|
||||
#define IDB_POSITIONS 50
|
||||
#define IDB_META_TYPE 2
|
||||
#define IDB_TYPE 0x69
|
||||
#define IDB_MAX_BOOT_BLOCKS 64
|
||||
|
||||
static bool ftl_mounted = false;
|
||||
|
||||
/* The size of the SYS volume, from ID block 1.
|
||||
*
|
||||
* ID block 0 is scrambled; ID block 1, the sector after it, is plain:
|
||||
*
|
||||
* +0 uint16_t LE blocks of bootloader
|
||||
* +2 uint16_t LE SYS volume size, MB
|
||||
*
|
||||
* A sector marked 0x69 whose ID block 1 gives a sane block count and a SYS
|
||||
* volume smaller than the chip is taken; descrambling ID block 0 to check its
|
||||
* signature would buy little over that. Returns 0 if none is found. */
|
||||
static uint32_t idb_sys_sectors(void)
|
||||
{
|
||||
const struct flash_geometry *geo = flash_get_geometry();
|
||||
uint8_t data[FLASH_SECTOR_SIZE], meta[FLASH_META_SIZE];
|
||||
uint32_t sectors = 0;
|
||||
uint32_t pos;
|
||||
|
||||
for (pos = 0; pos < IDB_POSITIONS && sectors == 0; pos++)
|
||||
{
|
||||
uint32_t raw = pos * IDB_STRIDE;
|
||||
uint32_t blocks, mb;
|
||||
|
||||
if (raw + 1 >= geo->total_sectors)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (flash_read_raw(raw, data, meta) != 0
|
||||
|| meta[IDB_META_TYPE] != IDB_TYPE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (flash_read_raw(raw + 1, data, meta) != 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
blocks = data[0] | (data[1] << 8);
|
||||
mb = data[2] | (data[3] << 8);
|
||||
|
||||
if (blocks > 0 && blocks <= IDB_MAX_BOOT_BLOCKS && mb > 0 &&
|
||||
mb * 2048 < geo->total_sectors)
|
||||
{
|
||||
sectors = mb * 2048;
|
||||
}
|
||||
}
|
||||
return sectors;
|
||||
}
|
||||
|
||||
uint32_t ftl_init(void)
|
||||
{
|
||||
/* TODO implement */
|
||||
return 0;
|
||||
struct ftl_a_config config;
|
||||
uint32_t ret = 0;
|
||||
|
||||
flash_init();
|
||||
|
||||
if (flash_layer_init() != 0)
|
||||
{
|
||||
ret = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
config.sys_sectors = idb_sys_sectors();
|
||||
#ifdef FTL_ALLOW_WRITE
|
||||
config.read_only = false;
|
||||
/* the rk2705 NAND bootloader's generation formats with flag 1; its
|
||||
* write logic is the same as the standard one's */
|
||||
config.alt_format_flag = 1;
|
||||
config.alt_format_writable = true;
|
||||
#else
|
||||
config.read_only = true;
|
||||
config.alt_format_flag = 1;
|
||||
config.alt_format_writable = false;
|
||||
#endif
|
||||
|
||||
if (config.sys_sectors == 0)
|
||||
{
|
||||
ret = 2; /* without it USER cannot be told from SYS */
|
||||
}
|
||||
else if (ftl_a_mount(&config) != FTL_A_OK)
|
||||
{
|
||||
ret = 3;
|
||||
}
|
||||
else if (ftl_a_capacity(FTL_A_VOL_USER) == 0)
|
||||
{
|
||||
ret = 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
ftl_mounted = true;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t ftl_read(uint32_t sector, uint32_t count, void* buffer)
|
||||
/* The FTL volume behind a drive. SYS is reachable only when exposed. */
|
||||
static int ftl_volume(int drive)
|
||||
{
|
||||
/* TODO implement */
|
||||
(void)sector;
|
||||
(void)count;
|
||||
(void)buffer;
|
||||
return 0;
|
||||
int volume = FTL_A_VOL_USER;
|
||||
|
||||
#ifdef HAVE_RK27XX_NAND_SYS
|
||||
if (drive == FTL_DRIVE_SYS)
|
||||
{
|
||||
volume = FTL_A_VOL_SYS;
|
||||
}
|
||||
#else
|
||||
(void)drive;
|
||||
#endif
|
||||
return volume;
|
||||
}
|
||||
|
||||
uint32_t ftl_write(uint32_t sector, uint32_t count, const void* buffer)
|
||||
static bool drive_valid(int drive)
|
||||
{
|
||||
/* TODO implement */
|
||||
(void)sector;
|
||||
(void)count;
|
||||
(void)buffer;
|
||||
return 0;
|
||||
return ftl_mounted && drive >= 0 && drive < FTL_NUM_DRIVES;
|
||||
}
|
||||
|
||||
uint32_t ftl_get_sectors(int drive)
|
||||
{
|
||||
uint32_t sectors = 0;
|
||||
|
||||
if (drive_valid(drive))
|
||||
{
|
||||
sectors = ftl_a_capacity(ftl_volume(drive));
|
||||
}
|
||||
return sectors;
|
||||
}
|
||||
|
||||
uint32_t ftl_read(int drive, uint32_t sector, uint32_t count, void *buffer)
|
||||
{
|
||||
uint32_t ret = 1;
|
||||
|
||||
if (drive_valid(drive))
|
||||
{
|
||||
ret = ftl_a_read(ftl_volume(drive), sector, buffer, count) ? 2 : 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t ftl_write(int drive, uint32_t sector, uint32_t count,
|
||||
const void *buffer)
|
||||
{
|
||||
uint32_t ret = 1;
|
||||
|
||||
#ifdef FTL_ALLOW_WRITE
|
||||
if (drive_valid(drive))
|
||||
{
|
||||
ret = ftl_a_write(ftl_volume(drive), sector, buffer, count) ? 2 : 0;
|
||||
}
|
||||
#else
|
||||
/* refuse rather than pretend: a silent success would let the filesystem
|
||||
* believe data was committed */
|
||||
(void)drive; (void)sector; (void)count; (void)buffer;
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t ftl_sync(void)
|
||||
{
|
||||
/* TODO implement */
|
||||
if (ftl_mounted)
|
||||
{
|
||||
ftl_a_sync();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
2136
firmware/target/arm/rk27xx/ftl-scheme-a.c
Normal file
2136
firmware/target/arm/rk27xx/ftl-scheme-a.c
Normal file
File diff suppressed because it is too large
Load diff
99
firmware/target/arm/rk27xx/ftl-scheme-a.h
Normal file
99
firmware/target/arm/rk27xx/ftl-scheme-a.h
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 "Scheme A" flash translation layer of rk27xx devices - the on-flash
|
||||
* format the original firmware of many rk2705/rk2706 players uses, read and
|
||||
* written compatibly so that the original firmware keeps working on the same
|
||||
* media. See ftl-scheme-a.c for how it works. */
|
||||
|
||||
#ifndef __FTL_SCHEME_A_H__
|
||||
#define __FTL_SCHEME_A_H__
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* The two volumes. SYS holds the original firmware and its resources; USER
|
||||
* is the music storage. */
|
||||
#define FTL_A_VOL_SYS 0
|
||||
#define FTL_A_VOL_USER 1
|
||||
|
||||
struct ftl_a_config
|
||||
{
|
||||
/* Size of the SYS volume. The one layout value the FTL cannot read from
|
||||
* its own tables: it is recorded in the boot area's ID block. */
|
||||
uint32_t sys_sectors;
|
||||
/* Never write, not even the repairs a mount normally makes. */
|
||||
bool read_only;
|
||||
/* A format flag accepted besides the standard 2 (0 = none), and whether
|
||||
* media carrying it may be written. See ftl_a_mount(). */
|
||||
uint8_t alt_format_flag;
|
||||
bool alt_format_writable;
|
||||
};
|
||||
|
||||
/* Why a mount failed */
|
||||
enum ftl_a_error
|
||||
{
|
||||
FTL_A_OK = 0,
|
||||
FTL_A_ERR_LAYOUT, /* the zone layout could not be recognised */
|
||||
FTL_A_ERR_NO_LOG_BLOCK, /* a zone has no remap log: damaged */
|
||||
FTL_A_ERR_ZERO_CAPACITY, /* the block counts never loaded */
|
||||
FTL_A_ERR_COUNTS_ERASED, /* ... or were read from an erased page */
|
||||
FTL_A_ERR_SYS_TOO_BIG, /* SYS larger than the chip */
|
||||
FTL_A_ERR_FORMAT_FLAG, /* not formatted, or an unknown generation */
|
||||
FTL_A_ERR_NEEDS_REPAIR, /* a log needs rewriting; not while read-only */
|
||||
};
|
||||
|
||||
/* How sure the mount is of the layout it recognised */
|
||||
enum ftl_a_confidence
|
||||
{
|
||||
FTL_A_CONF_NONE = 0,
|
||||
FTL_A_CONF_CONFIRMED, /* derived and cross-checked: writable */
|
||||
FTL_A_CONF_ASSUMED, /* partly assumed: read-only */
|
||||
};
|
||||
|
||||
struct ftl_a_status
|
||||
{
|
||||
enum ftl_a_error error;
|
||||
enum ftl_a_confidence confidence;
|
||||
bool writable;
|
||||
uint8_t reserve_base; /* first reserve-pool entry of a zone table */
|
||||
uint8_t sys_zone; /* zone holding the boot area */
|
||||
uint8_t sys_offset; /* boot-area blocks at the start of it */
|
||||
uint8_t base_fits; /* reserve bases that fit the media; 1 = sure */
|
||||
uint8_t format_flag; /* as read from the media */
|
||||
};
|
||||
|
||||
/* Mount the media. Returns FTL_A_OK or the reason it failed. Even a
|
||||
* successful mount may be read-only - see ftl_a_get_status(). */
|
||||
enum ftl_a_error ftl_a_mount(const struct ftl_a_config *config);
|
||||
void ftl_a_get_status(struct ftl_a_status *status);
|
||||
|
||||
/* Sectors on a volume; 0 when not mounted */
|
||||
uint32_t ftl_a_capacity(int volume);
|
||||
|
||||
/* Return 0, or non-zero if the request was refused or a sector could not be
|
||||
* read reliably. Sectors are 512 bytes. */
|
||||
int ftl_a_read(int volume, uint32_t sector, void *buf, uint32_t count);
|
||||
int ftl_a_write(int volume, uint32_t sector, const void *buf, uint32_t count);
|
||||
|
||||
/* Write out everything still held in RAM */
|
||||
void ftl_a_sync(void);
|
||||
|
||||
#endif /* __FTL_SCHEME_A_H__ */
|
||||
|
|
@ -24,21 +24,26 @@
|
|||
#include "config.h"
|
||||
#include "inttypes.h"
|
||||
|
||||
#ifdef BOOTLOADER
|
||||
/* Bootloaders don't need write access */
|
||||
#define FTL_READONLY
|
||||
/* The drives the NAND presents: USER, and SYS only when the target exposes
|
||||
* it - see HAVE_RK27XX_NAND_SYS in the target config. */
|
||||
#ifdef HAVE_RK27XX_NAND_SYS
|
||||
#define FTL_DRIVE_SYS 0
|
||||
#define FTL_DRIVE_USER 1
|
||||
#define FTL_NUM_DRIVES 2
|
||||
#else
|
||||
#define FTL_DRIVE_USER 0
|
||||
#define FTL_NUM_DRIVES 1
|
||||
#endif
|
||||
|
||||
/* Pointer to an info structure regarding the flash type used */
|
||||
extern const struct nand_device_info_type* ftl_nand_type;
|
||||
|
||||
/* Number of banks we detected a chip on */
|
||||
extern uint32_t ftl_banks;
|
||||
|
||||
uint32_t ftl_init(void);
|
||||
uint32_t ftl_read(uint32_t sector, uint32_t count, void* buffer);
|
||||
uint32_t ftl_write(uint32_t sector, uint32_t count, const void* buffer);
|
||||
uint32_t ftl_read(int drive, uint32_t sector, uint32_t count, void* buffer);
|
||||
uint32_t ftl_write(int drive, uint32_t sector, uint32_t count,
|
||||
const void* buffer);
|
||||
uint32_t ftl_sync(void);
|
||||
|
||||
/* Usable sectors on a logical disk, 0 if not mounted. Comes from the FTL's
|
||||
* own tables, not from raw block geometry - see ftl-rk27xx.c. */
|
||||
uint32_t ftl_get_sectors(int drive);
|
||||
|
||||
|
||||
#endif
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue