blob: 9b1ec4689c1f638e6af40f2a696069c80c3064be [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This is a conversion script for taking nand images that were dumped using the uboot "nand read"
// and "nand read.raw" commands, then converting them to the "nandoob" format which is used by our
// more common dumping tool. Sometimes the uboot method is more convenient for folks to use, so this
// provides a way to make it consumable by the ftl-volume-extractor. The "nandoob" format is a
// simple format where each "write" page is immediately followed by the associated spare (aka OOB)
// data.
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
// === Constants regarding the format of images from uboot "nand read.raw" ===
// Each ECC page is 1024 bytes followed by 56 bytes of OOB data (ECC + spare).
const size_t kOobSize = 56;
// The first two bytes of the OOB are part of the spare for the page.
const size_t kSparePerEccPage = 2;
const size_t kEccPageSize = 1024;
// Each "write" page has 4 ECC pages.
const size_t kEccPagePerWritePage = 4;
const size_t kSpareSize = kSparePerEccPage * kEccPagePerWritePage;
const size_t kPagesPerBlock = 64;
// Each "write" page has 32 trailing bytes for Toshiba. None for Micron. These
// trailing bytes tend to be all 0xFF, unless it is a factory bad block. In
// which case it is all 0x00. We ignore these bytes in any case.
const size_t kToshibaSkipSize = 32;
const size_t kMicronSkipSize = 0;
// Bad blocks should mark their first byte of OOB with 0xFF, but since some bit
// errors can occur the more correct way is to verify that there are more zeroes
// than ones in the byte.
bool IsBadBlockByte(uint8_t byte) {
int zeros = 0;
for (int i = 0; i < 8; ++i) {
if ((byte & 1) == 0) {
++zeros;
}
byte = byte >> 1;
}
return (zeros > 4);
}
void PrintUsage(char* bin_name) {
fprintf(stderr,
"Usage: %s [--toshiba|--micron] "
"raw_image_with_oob ecc_corrected_volume_image output_nand_oob\n",
bin_name);
fprintf(stderr,
"Combines info from a normal read with ECC with the OOB information in a raw\n"
"image file to build a \"nandoob\" formatted file which is parseable by the\n"
"ftl-volume-extractor\n");
fprintf(stderr, " --toshiba: Configure to read from Toshiba nand chips (default)\n");
fprintf(stderr, " --micron: Configure to read from Micron nand chips\n");
fprintf(stderr, " raw_image_with_oob: Path to image file from uboot \"nand read.raw\"\n");
fprintf(stderr, " ecc_corrected_volume_image: Path to image file from uboot \"nand read\"\n");
fprintf(stderr, " output_nand_oob: Path to output nandoob format for parsing\n");
}
int main(int argc, char** argv) {
if (argc < 4 || argc > 5) {
PrintUsage(argv[0]);
return 1;
}
int arg = 1;
size_t skip_size = kToshibaSkipSize;
if (strncmp(argv[arg], "--", 2) == 0) {
if (strcmp(argv[arg], "--micron") == 0) {
skip_size = kMicronSkipSize;
} else if (strcmp(argv[arg], "--toshiba") != 0) {
fprintf(stderr, "Unrecognized option: %s\n", argv[arg]);
PrintUsage(argv[0]);
return 1;
}
++arg;
}
FILE* raw_file = fopen(argv[arg++], "r");
if (raw_file == NULL) {
fprintf(stderr, "Failed to open input raw image file: %s\n", argv[arg - 1]);
return 2;
}
FILE* data_file = fopen(argv[arg++], "r");
if (data_file == NULL) {
fprintf(stderr, "Failed to open input data image file: %s\n", argv[arg - 1]);
return 2;
}
FILE* out_file = fopen(argv[arg++], "w");
if (out_file == NULL) {
fprintf(stderr, "Failed to open ouput data + oob image file: %s\n", argv[arg - 1]);
return 2;
}
uint8_t volume_buffer[kEccPageSize];
uint8_t raw_buffer[kEccPageSize];
uint8_t oob_buffer[kOobSize];
uint8_t spare_buffer[kSpareSize];
size_t pages = 0;
bool bad_block = false;
bool eof = false;
while (!feof(raw_file)) {
for (size_t ecc_page = 0; ecc_page < kEccPagePerWritePage; ++ecc_page) {
size_t data_size = fread(raw_buffer, kEccPageSize, 1, raw_file);
if (data_size == 0) {
if (feof(raw_file)) {
// This will break the outer loop.
eof = true;
break;
}
fprintf(stderr, "Failed to read raw data at %lu, expected %lu bytes.\n", pages,
kEccPageSize);
return 3;
}
size_t oob_size = fread(oob_buffer, kOobSize, 1, raw_file);
if (oob_size == 0) {
fprintf(stderr, "Failed to read oob at %lu, expected %lu bytes.\n", pages, kOobSize);
return 3;
}
// For the first page of a block, check for the bad block mark.
if (pages % kPagesPerBlock == 0 && ecc_page == 0) {
if (IsBadBlockByte(oob_buffer[0])) {
fprintf(stderr, "Found bad block at %lu\n", pages / kPagesPerBlock);
bad_block = true;
} else {
bad_block = false;
}
}
// When uboot nand.read detects bad blocks, it just skips to the next one.
// So we don't want to progress the file pointer.
if (bad_block) {
// Populate bad blocks with zeroes.
memset(volume_buffer, 0, kEccPageSize);
memset(oob_buffer, 0, kOobSize);
} else {
// Read the volume data file for the ECC'd version of the data to write
// to the output file.
size_t data_size = fread(volume_buffer, kEccPageSize, 1, data_file);
if (data_size == 0) {
fprintf(stderr, "Failed to read corrected data at %lu, expected %lu bytes.\n", pages,
kEccPageSize);
return 3;
}
}
size_t write_size = fwrite(volume_buffer, kEccPageSize, 1, out_file);
if (write_size == 0) {
fprintf(stderr, "Failed to write data of size %lu.\n", kEccPageSize);
return 4;
}
// Accumulate the Spare bytes for writing out after the page.
memcpy(&spare_buffer[ecc_page * kSparePerEccPage], oob_buffer, kSparePerEccPage);
}
if (eof) {
break;
}
size_t write_size = fwrite(spare_buffer, kSpareSize, 1, out_file);
if (write_size == 0) {
fprintf(stderr, "Failed to write oob data of size %lu.\n", kOobSize);
return 4;
}
if (skip_size > 0) {
// Advance the file pointer past skippable bytes.
size_t data_size = fread(raw_buffer, skip_size, 1, raw_file);
if (data_size == 0) {
fprintf(stderr, "Failed to read skippable bytes.\n");
return 4;
}
}
pages++;
}
fclose(raw_file);
fclose(data_file);
fclose(out_file);
return 0;
}