/*************************************************************************** * __________ __ ___. * 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 #include #include #include #include #include #include #include #include "misc.h" #include #include #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 \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 \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; }