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:
Marcin Bukat 2013-09-02 12:35:47 +02:00
parent b97cdc8f5e
commit f182a11f33
5 changed files with 1782 additions and 0 deletions

View file

@ -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.

View file

@ -0,0 +1,7 @@
all: nandextract
nandextract: nandextract.c libbch.c
gcc -g -std=c99 -o $@ -W -Wall $^
clean:
rm -fr nandextract

File diff suppressed because it is too large Load diff

View 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 */

View 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;
}