mirror of
https://github.com/Rockbox/rockbox.git
synced 2025-10-13 10:07:38 -04:00
rk27utils: Add nandextract utility
This quick and dirty utility allows to extract nand bootloader from raw 1st nand block dump. I post it mainly to somewhat document how BCH error correction engine of the rk27xx works. Change-Id: I37ca91add7d372e3576d2722afc946d0f08971a9
This commit is contained in:
parent
b97cdc8f5e
commit
f182a11f33
5 changed files with 1782 additions and 0 deletions
|
@ -35,3 +35,37 @@ This directory contains tool which sends custom scsi commands to the
|
|||
rockchip player.
|
||||
|
||||
You need libusb-1.0 + header files in order to compile this utility.
|
||||
|
||||
nandextract
|
||||
This directory contains quick and dirty tool which allows to extract
|
||||
nand bootloader from raw dump of the first nand block. The main reason
|
||||
I post this tool is to somewhat document error correction scheme used by
|
||||
rk27xx chip. The tool implements BCH error correction processing with
|
||||
help of bch library taken from linux kernel (and slightly modified to
|
||||
compile standalone). Error correction is SUPER important as the nands used
|
||||
in cheap rk27 players have quite high error rates.
|
||||
|
||||
Nand controler in rk27xx chip implements hw BCH error correction engine.
|
||||
The documentation is lacking so this info was obtained from RE and
|
||||
various other sources.
|
||||
The data on the nand is stored in 528 bytes long chunks - 512 bytes
|
||||
of actual data followed by 3 bytes of metadata (used by FTL layer to mark
|
||||
special sectors) followed by 13 bytes of BCH ECC. BCH algorithm
|
||||
uses m=13, t=8 and primitive polynomial 0x25af. Special masking
|
||||
is used such as empty sector (with all 0xff) gives all 0xff ECC bytes.
|
||||
Quoting e-mail from Ivan Djelic (the author of bch lib in linux):
|
||||
To summarize, the steps needed to compute the rk27xx ecc are the following:
|
||||
1. Reverse bits in each input byte
|
||||
2. Call encode_bch()
|
||||
3. Reverse output bits in each computed ecc byte
|
||||
4. Add a polynomial in order to get only 0xff ecc bytes for a blank page
|
||||
For more details you need to read the code.
|
||||
|
||||
Another quirk is that rom loader assumes that there are 4 sectors in each
|
||||
nand page. This is actually not true for newer nand chips with page size
|
||||
bigger then 2k. That means that on newer 4k page chips only first half of
|
||||
every page is used in nand bootloader area. This is for compatibility reasons
|
||||
most probably.
|
||||
|
||||
Finally, every 512 bytes block of data is encoded with rc4 algorithm.
|
||||
The key and routine were recovered from rk27xx rom dump by AleMaxx.
|
||||
|
|
7
utils/rk27utils/nandextract/Makefile
Normal file
7
utils/rk27utils/nandextract/Makefile
Normal file
|
@ -0,0 +1,7 @@
|
|||
all: nandextract
|
||||
|
||||
nandextract: nandextract.c libbch.c
|
||||
gcc -g -std=c99 -o $@ -W -Wall $^
|
||||
|
||||
clean:
|
||||
rm -fr nandextract
|
1393
utils/rk27utils/nandextract/libbch.c
Normal file
1393
utils/rk27utils/nandextract/libbch.c
Normal file
File diff suppressed because it is too large
Load diff
113
utils/rk27utils/nandextract/libbch.h
Normal file
113
utils/rk27utils/nandextract/libbch.h
Normal file
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Generic binary BCH encoding/decoding library
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Copyright © 2011 Parrot S.A.
|
||||
*
|
||||
* Author: Ivan Djelic <ivan.djelic@parrot.com>
|
||||
*
|
||||
* Description:
|
||||
*
|
||||
* This library provides runtime configurable encoding/decoding of binary
|
||||
* Bose-Chaudhuri-Hocquenghem (BCH) codes.
|
||||
*/
|
||||
#ifndef _BCH_H
|
||||
#define _BCH_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(CONFIG_BCH_CONST_PARAMS)
|
||||
#define GF_M(_p) (CONFIG_BCH_CONST_M)
|
||||
#define GF_T(_p) (CONFIG_BCH_CONST_T)
|
||||
#define GF_N(_p) ((1 << (CONFIG_BCH_CONST_M))-1)
|
||||
#else
|
||||
#define GF_M(_p) ((_p)->m)
|
||||
#define GF_T(_p) ((_p)->t)
|
||||
#define GF_N(_p) ((_p)->n)
|
||||
#endif
|
||||
|
||||
#define BCH_ECC_WORDS(_p) DIV_ROUND_UP(GF_M(_p)*GF_T(_p), 32)
|
||||
#define BCH_ECC_BYTES(_p) DIV_ROUND_UP(GF_M(_p)*GF_T(_p), 8)
|
||||
|
||||
#ifndef dbg
|
||||
#define dbg(_fmt, args...) do {} while (0)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* represent a polynomial over GF(2^m)
|
||||
*/
|
||||
struct gf_poly {
|
||||
unsigned int deg; /* polynomial degree */
|
||||
unsigned int c[0]; /* polynomial terms */
|
||||
};
|
||||
|
||||
/* given its degree, compute a polynomial size in bytes */
|
||||
#define GF_POLY_SZ(_d) (sizeof(struct gf_poly)+((_d)+1)*sizeof(unsigned int))
|
||||
|
||||
/* polynomial of degree 1 */
|
||||
struct gf_poly_deg1 {
|
||||
struct gf_poly poly;
|
||||
unsigned int c[2];
|
||||
};
|
||||
|
||||
/**
|
||||
* struct bch_control - BCH control structure
|
||||
* @m: Galois field order
|
||||
* @n: maximum codeword size in bits (= 2^m-1)
|
||||
* @t: error correction capability in bits
|
||||
* @ecc_bits: ecc exact size in bits, i.e. generator polynomial degree (<=m*t)
|
||||
* @ecc_bytes: ecc max size (m*t bits) in bytes
|
||||
* @a_pow_tab: Galois field GF(2^m) exponentiation lookup table
|
||||
* @a_log_tab: Galois field GF(2^m) log lookup table
|
||||
* @mod8_tab: remainder generator polynomial lookup tables
|
||||
* @ecc_buf: ecc parity words buffer
|
||||
* @ecc_buf2: ecc parity words buffer
|
||||
* @xi_tab: GF(2^m) base for solving degree 2 polynomial roots
|
||||
* @syn: syndrome buffer
|
||||
* @cache: log-based polynomial representation buffer
|
||||
* @elp: error locator polynomial
|
||||
* @poly_2t: temporary polynomials of degree 2t
|
||||
*/
|
||||
struct bch_control {
|
||||
unsigned int m;
|
||||
unsigned int n;
|
||||
unsigned int t;
|
||||
unsigned int ecc_bits;
|
||||
unsigned int ecc_bytes;
|
||||
/* private: */
|
||||
uint16_t *a_pow_tab;
|
||||
uint16_t *a_log_tab;
|
||||
uint32_t *mod8_tab;
|
||||
uint32_t *ecc_buf;
|
||||
uint32_t *ecc_buf2;
|
||||
unsigned int *xi_tab;
|
||||
unsigned int *syn;
|
||||
int *cache;
|
||||
struct gf_poly *elp;
|
||||
struct gf_poly *poly_2t[4];
|
||||
};
|
||||
|
||||
struct bch_control *init_bch(int m, int t, unsigned int prim_poly);
|
||||
|
||||
void free_bch(struct bch_control *bch);
|
||||
|
||||
void encode_bch(struct bch_control *bch, const uint8_t *data,
|
||||
unsigned int len, uint8_t *ecc);
|
||||
|
||||
int decode_bch(struct bch_control *bch, const uint8_t *data, unsigned int len,
|
||||
const uint8_t *recv_ecc, const uint8_t *calc_ecc,
|
||||
const unsigned int *syn, unsigned int *errloc);
|
||||
|
||||
#endif /* _BCH_H */
|
235
utils/rk27utils/nandextract/nandextract.c
Normal file
235
utils/rk27utils/nandextract/nandextract.c
Normal file
|
@ -0,0 +1,235 @@
|
|||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "libbch.h"
|
||||
|
||||
#define SECTOR_DATA_SIZE 512
|
||||
#define SECTOR_META_SIZE 3
|
||||
#define SECTOR_ECC_SIZE 13
|
||||
#define SECTOR_SIZE (SECTOR_DATA_SIZE + SECTOR_META_SIZE + SECTOR_ECC_SIZE)
|
||||
|
||||
/* scramble mode */
|
||||
enum {
|
||||
CONTINOUS_ENC, /* scramble whole block at once */
|
||||
PAGE_ENC /* nand bootloader is scrambled in 0x200 chunks */
|
||||
};
|
||||
|
||||
static uint8_t reverse_bits(uint8_t b)
|
||||
{
|
||||
return (((b & 0x80) >> 7)|
|
||||
((b & 0x40) >> 5)|
|
||||
((b & 0x20) >> 3)|
|
||||
((b & 0x10) >> 1)|
|
||||
((b & 0x08) << 1)|
|
||||
((b & 0x04) << 3)|
|
||||
((b & 0x02) << 5)|
|
||||
((b & 0x01) << 7));
|
||||
}
|
||||
|
||||
static int libbch_decode_sec(struct bch_control *bch, uint8_t *inbuf, uint8_t *outbuf)
|
||||
{
|
||||
unsigned int errloc[8];
|
||||
static const uint8_t mask[13] = {
|
||||
0x4e, 0x8c, 0x9d, 0x52,
|
||||
0x2d, 0x6c, 0x7c, 0xcb,
|
||||
0xc3, 0x12, 0x14, 0x19,
|
||||
0x37,
|
||||
};
|
||||
|
||||
int i, err_num = 0;
|
||||
|
||||
/* ecc masking polynomial */
|
||||
for (i=0; i<SECTOR_ECC_SIZE; i++)
|
||||
inbuf[SECTOR_DATA_SIZE+SECTOR_META_SIZE+i] ^= mask[i];
|
||||
|
||||
/* fix ordering of input bits */
|
||||
for (i = 0; i < SECTOR_SIZE; i++)
|
||||
inbuf[i] = reverse_bits(inbuf[i]);
|
||||
|
||||
err_num = decode_bch(bch, inbuf,
|
||||
(SECTOR_SIZE - SECTOR_ECC_SIZE),
|
||||
&inbuf[SECTOR_SIZE - SECTOR_ECC_SIZE],
|
||||
NULL, NULL, errloc);
|
||||
|
||||
/* apply fixups */
|
||||
for(i=0; i<err_num; i++)
|
||||
inbuf[errloc[i]/8] ^= 1 << (errloc[i] % 8);
|
||||
|
||||
/* reverse bits back (data part only), remining bytes are scratched */
|
||||
for (i = 0; i < SECTOR_DATA_SIZE; i++)
|
||||
outbuf[i] = reverse_bits(inbuf[i]);
|
||||
|
||||
return err_num;
|
||||
}
|
||||
|
||||
/* scrambling/descrambling reverse engineered by AleMaxx */
|
||||
static void encode_page(uint8_t *inpg, uint8_t *outpg, const int size)
|
||||
{
|
||||
|
||||
uint8_t key[] = {
|
||||
0x7C, 0x4E, 0x03, 0x04,
|
||||
0x55, 0x05, 0x09, 0x07,
|
||||
0x2D, 0x2C, 0x7B, 0x38,
|
||||
0x17, 0x0D, 0x17, 0x11
|
||||
};
|
||||
int i, i3, x, val, idx;
|
||||
|
||||
uint8_t key1[0x100];
|
||||
uint8_t key2[0x100];
|
||||
|
||||
for (i=0; i<0x100; i++) {
|
||||
key1[i] = i;
|
||||
key2[i] = key[i&0xf];
|
||||
}
|
||||
|
||||
i3 = 0;
|
||||
for (i=0; i<0x100; i++) {
|
||||
x = key1[i];
|
||||
i3 = key1[i] + i3;
|
||||
i3 += key2[i];
|
||||
i3 &= 0xff;
|
||||
key1[i] = key1[i3];
|
||||
key1[i3] = x;
|
||||
}
|
||||
|
||||
idx = 0;
|
||||
for (i=0; i<size; i++) {
|
||||
x = key1[(i+1) & 0xff];
|
||||
val = x;
|
||||
idx = (x + idx) & 0xff;
|
||||
key1[(i+1) & 0xff] = key1[idx];
|
||||
key1[idx] = (x & 0xff);
|
||||
val = (key1[(i+1)&0xff] + x) & 0xff;
|
||||
val = key1[val];
|
||||
outpg[i] = val ^ inpg[i];
|
||||
}
|
||||
}
|
||||
|
||||
/* returns offset in bytes of the sector
|
||||
* NOTE: bootrom assumes 4 secs per page (regardles of actual pagesize)
|
||||
*/
|
||||
static int offset(int sec_num, int page_size, int rom)
|
||||
{
|
||||
int sec_per_page, page_num, page_offset;
|
||||
|
||||
if (rom)
|
||||
sec_per_page = 4;
|
||||
else
|
||||
sec_per_page = page_size / SECTOR_SIZE;
|
||||
|
||||
page_num = sec_num / sec_per_page;
|
||||
page_offset = sec_num % sec_per_page;
|
||||
|
||||
printf("Sec per page: %d\n", sec_per_page);
|
||||
printf("Page num: %d\n", page_num);
|
||||
printf("Offset in page (sec): %d\n", page_offset);
|
||||
printf("Offset in file (bytes): %d (0x%0x)\n", (page_num * page_size) +
|
||||
(page_offset * SECTOR_SIZE),
|
||||
(page_num * page_size) +
|
||||
(page_offset * SECTOR_SIZE));
|
||||
|
||||
return ((page_num * page_size) + (page_offset * SECTOR_SIZE));
|
||||
}
|
||||
|
||||
static int sector_read(FILE *fp, void *buff, int sec_num, int nand_page_size, struct bch_control *bch)
|
||||
{
|
||||
int ret;
|
||||
int file_offset = offset(sec_num, nand_page_size, 1);
|
||||
uint8_t inbuf[SECTOR_SIZE];
|
||||
uint8_t outbuf[SECTOR_SIZE];
|
||||
|
||||
if (fp == NULL)
|
||||
return -1;
|
||||
|
||||
/* seek to the begining of the data */
|
||||
fseek(fp, file_offset, SEEK_SET);
|
||||
|
||||
/* read into the buffer */
|
||||
ret = fread(inbuf, 1, SECTOR_SIZE, fp);
|
||||
|
||||
if (ret != SECTOR_SIZE)
|
||||
{
|
||||
return -2;
|
||||
}
|
||||
|
||||
ret = libbch_decode_sec(bch, inbuf, outbuf);
|
||||
|
||||
if (ret)
|
||||
{
|
||||
printf("LIBBCH Data %d error(s) in sector %d\n", ret, sec_num);
|
||||
}
|
||||
|
||||
memcpy(buff, outbuf, SECTOR_DATA_SIZE);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
FILE *ifp, *ofp;
|
||||
void *obuf;
|
||||
int i, size, sector, num_sectors, nand_page_size;
|
||||
char *infile, *outfile, *ptr;
|
||||
|
||||
struct bch_control *bch;
|
||||
|
||||
if (argc < 6)
|
||||
{
|
||||
printf("Usage: %s infile outfile start_sector num_sectors nand_page_size\n", argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
infile = argv[1];
|
||||
outfile = argv[2];
|
||||
sector = atoi(argv[3]);
|
||||
num_sectors = atoi(argv[4]);
|
||||
nand_page_size = atoi(argv[5]);
|
||||
|
||||
size = SECTOR_DATA_SIZE * num_sectors;
|
||||
obuf = malloc(size);
|
||||
|
||||
if (obuf == NULL)
|
||||
{
|
||||
printf("Error allocating %d bytes of buffer\n", size);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ifp = fopen(infile, "rb");
|
||||
|
||||
if (ifp == NULL)
|
||||
{
|
||||
printf("Cannot open %s file\n", infile);
|
||||
free(obuf);
|
||||
return -2;
|
||||
}
|
||||
|
||||
ofp = fopen(outfile, "wb");
|
||||
|
||||
if (ifp == NULL)
|
||||
{
|
||||
printf("Cannot open %s file\n", outfile);
|
||||
fclose(ifp);
|
||||
free(obuf);
|
||||
return -3;
|
||||
}
|
||||
|
||||
bch = init_bch(13, 8, 0x25af);
|
||||
|
||||
ptr = (char *)obuf;
|
||||
for(i=0; i<num_sectors; i++)
|
||||
{
|
||||
sector_read(ifp, ptr, sector++, nand_page_size, bch);
|
||||
encode_page((uint8_t *)ptr, (uint8_t *)ptr, SECTOR_DATA_SIZE);
|
||||
ptr += SECTOR_DATA_SIZE;
|
||||
}
|
||||
|
||||
fwrite(obuf, 1, size, ofp);
|
||||
|
||||
fclose(ifp);
|
||||
fclose(ofp);
|
||||
free(obuf);
|
||||
|
||||
free_bch(bch);
|
||||
return 0;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue