rockbox/utils/nwztools/emmctools/emmctool.c

287 lines
7.6 KiB
C
Raw Normal View History

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2012 Amaury Pouly
*
* 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.
*
****************************************************************************/
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <stdarg.h>
#include <ctype.h>
#include "misc.h"
#include <sys/stat.h>
#include <openssl/md5.h>
#include "nvp.h"
bool g_debug = false;
static char *g_out_prefix = NULL;
static FILE *g_in_file = NULL;
bool g_force = false;
static int g_nvp_node = -1;
#define let_the_force_flow(x) do { if(!g_force) return x; } while(0)
#define continue_the_force(x) if(x) let_the_force_flow(x)
#define check_field(v_exp, v_have, str_ok, str_bad) \
if((v_exp) != (v_have)) \
{ cprintf(RED, str_bad); let_the_force_flow(__LINE__); } \
else { cprintf(RED, str_ok); }
#define errorf(...) do { cprintf(GREY, __VA_ARGS__); return __LINE__; } while(0)
static void print_hex(void *p, int size, int unit)
{
uint8_t *p8 = p;
uint16_t *p16 = p;
uint32_t *p32 = p;
for(int i = 0; i < size; i += unit, p8++, p16++, p32++)
{
if(i != 0 && (i % 16) == 0)
printf("\n");
if(unit == 1)
printf(" %02x", *p8);
else if(unit == 2)
printf(" %04x", *p16);
else
printf(" %08x", *p32);
}
}
#define SECTOR 512u
#define EMMC_MINIBOOT_START 0
#define EMMC_MINIBOOT_SIZE (8 * SECTOR)
#define EMMC_UBOOT_START (8 * SECTOR)
#define EMMC_FU_LINUX_START (512 * SECTOR)
#define EMMC_LINUX_START (66048 * SECTOR)
#define EMMC_NVP_START ((512 + 32768) * SECTOR)
#define EMMC_NVP_SIZE (30720 * SECTOR)
#define print_entry(begin, end, ...) \
do{ cprintf(YELLOW, " %08x %08x ", begin, end); cprintf(GREEN, __VA_ARGS__); } while(0)
static int read(uint32_t offset, uint32_t size, void *buf)
{
if(fseek(g_in_file, offset, SEEK_SET))
errorf("Cannot seek in file: %m\n");
if(fread(buf, size, 1, g_in_file) != 1)
errorf("Cannot read in file: %m\n");
return 0;
}
static int nvp_read(uint32_t offset, uint32_t size, void *buf)
{
if(offset + size > EMMC_NVP_SIZE)
errorf("nvp read out of nvp area\n");
return read(offset + EMMC_NVP_START, size, buf);
}
// returns size or 0
static uint32_t do_image(uint32_t start, const char *name)
{
uint32_t size;
int ret = read(start, sizeof(size), &size);
if(ret) return ret;
/* actual uboot size contains 4 bytes for the size, 4 for the crc pad and
* must be ronded to the next sector */
size = ROUND_UP(size + 8, SECTOR);
print_entry(start, start + size, name);
/* Check U-Boot crc (must be 0) */
uint32_t crc_buffer[SECTOR / 4];
uint32_t crc = 0;
uint32_t pos = start + 4;
uint32_t rem_size = size - 4;
while(rem_size)
{
ret = read(pos, SECTOR, crc_buffer);
if(ret) return ret;
uint32_t sz = MIN(rem_size, SECTOR);
for(unsigned i = 0; i < sz / 4; i++)
crc ^= crc_buffer[i];
pos += sz;
rem_size -= sz;
}
if(crc == 0)
{
cprintf(RED, " (CRC Ok)\n");
return size;
}
else
{
cprintf(RED, " (CRC Mismatch)\n");
return 0;
}
}
static int do_emmc(void)
{
cprintf(BLUE, "eMMC map\n");
cprintf(RED, " begin end comment\n");
print_entry(EMMC_MINIBOOT_START, EMMC_MINIBOOT_START + EMMC_MINIBOOT_SIZE, "eMMC Mini Boot\n");
uint32_t uboot_size = do_image(EMMC_UBOOT_START, "U-Boot");
if(!uboot_size)
return 1;
uint32_t fulinux_start = EMMC_UBOOT_START + uboot_size;
uint32_t fulinux_size = do_image(fulinux_start, "FU Linux");
if(!fulinux_size)
return 1;
uint32_t fu_initrd_size = do_image(EMMC_FU_LINUX_START, "FU initrd");
if(!fu_initrd_size)
return 1;
print_entry(EMMC_NVP_START, EMMC_NVP_START + EMMC_NVP_SIZE, "NVP\n");
uint32_t linux_size = do_image(EMMC_LINUX_START, "Linux");
if(!linux_size)
return 1;
int ret = nvp_info();
continue_the_force(ret);
return 0;
}
static int do_nvp_extract(void)
{
if(!g_out_prefix)
{
cprintf(GREY, "You must specify an output prefix to extract a NVP node\n");
return 1;
}
if(!nvp_is_valid_node(g_nvp_node))
{
cprintf(GREY, "Invalid NVP node %d\n", g_nvp_node);
return 3;
}
FILE *f = fopen(g_out_prefix, "wb");
if(!f)
{
cprintf(GREY, "Cannot open output file: %m\n");
return 2;
}
int size = nvp_get_node_size(g_nvp_node);
void *buffer = malloc(size);
int ret = nvp_read_node(g_nvp_node, 0, buffer, size);
if(ret < 0)
cprintf(GREY, "NVP read error: %d\n", ret);
else
{
cprintf(YELLOW, "%d ", ret);
cprintf(GREEN, "bytes written\n");
fwrite(buffer, 1, ret, f);
}
free(buffer);
fclose(f);
return 0;
}
static void usage(void)
{
printf("Usage: emmctool [options] img\n");
printf("Options:\n");
printf(" -o <prefix>\t\tSet output prefix\n");
printf(" -f/--force\t\tForce to continue on errors\n");
printf(" -?/--help\t\tDisplay this message\n");
printf(" -d/--debug\t\tDisplay debug messages\n");
printf(" -c/--no-color\t\tDisable color output\n");
printf(" -e/--nvp-ex <node>\tExtract a NVP node\n");
exit(1);
}
int main(int argc, char **argv)
{
while(1)
{
static struct option long_options[] =
{
{"help", no_argument, 0, '?'},
{"debug", no_argument, 0, 'd'},
{"no-color", no_argument, 0, 'c'},
{"force", no_argument, 0, 'f'},
{"nvp-ex", required_argument, 0, 'e'},
{0, 0, 0, 0}
};
int c = getopt_long(argc, argv, "?dcfo:e:", long_options, NULL);
if(c == -1)
break;
switch(c)
{
case -1:
break;
case 'c':
enable_color(false);
break;
case 'd':
g_debug = true;
break;
case 'f':
g_force = true;
break;
case '?':
usage();
break;
case 'o':
g_out_prefix = optarg;
break;
case 'e':
g_nvp_node = strtoul(optarg, NULL, 0);
break;
default:
abort();
}
}
if(argc - optind != 1)
{
usage();
return 1;
}
g_in_file = fopen(argv[optind], "rb");
if(g_in_file == NULL)
{
perror("Cannot open boot file");
return 1;
}
int ret = nvp_init(EMMC_NVP_SIZE, &nvp_read, g_debug);
if(ret) return ret;
ret = do_emmc();
if(ret == 0 && g_nvp_node >= 0)
ret = do_nvp_extract();
fclose(g_in_file);
color(OFF);
return ret;
}