/*************************************************************************** * __________ __ ___. * 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. * ****************************************************************************/ #define _POSIX_C_SOURCE 200809L /* for strdup */ #include #include #include #include #include #include #include #include #include "crypto.h" #include "elf.h" #include "sb1.h" #include "misc.h" #define ROUND_UP(val, round) ((((val) + (round) - 1) / (round)) * (round)) /** * Globals */ char *g_output_file; bool g_critical; bool g_final; bool g_strict = true; uint32_t g_jump_arg; /** * Helpers */ typedef char* (*get_next_arg_t)(void *user); struct cmd_line_next_arg_user_t { int argc; char **argv; }; static char *cmd_line_next_arg(void *user) { struct cmd_line_next_arg_user_t *uu = user; if(uu->argc == 0) return NULL; uu->argc--; uu->argv++; return *(uu->argv - 1); } static int sb1_add_inst(struct sb1_file_t *sb, struct sb1_inst_t *insts, int nr_insts) { sb->insts = augment_array(sb->insts, sizeof(struct sb1_inst_t), sb->nr_insts, insts, nr_insts); sb->nr_insts += nr_insts; return 0; } static int sb1_add_load(struct sb1_file_t *sb, void *data, int size, uint32_t addr) { while(size > 0) { int len = MIN(size, SB1_CMD_MAX_LOAD_SIZE); struct sb1_inst_t inst; memset(&inst, 0, sizeof(inst)); inst.cmd = SB1_INST_LOAD; inst.size = len; inst.addr = addr; inst.critical = g_critical; inst.data = xmalloc(len); memcpy(inst.data, data, len); if(g_debug) printf("Add instruction: load %#x bytes at %#x\n", len, addr); int ret = sb1_add_inst(sb, &inst, 1); if(ret < 0) return ret; data += len; size -= len; addr += len; } return 0; } static int sb1_add_switch(struct sb1_file_t *sb, uint32_t driver) { struct sb1_inst_t inst; memset(&inst, 0, sizeof(inst)); inst.cmd = SB1_INST_MODE; inst.critical = g_critical; inst.mode = driver; if(g_debug) printf("Add instruction: switch driver to %#x\n", driver); g_final = true; return sb1_add_inst(sb, &inst, 1); } static int sb1_add_sdram(struct sb1_file_t *sb, uint32_t cs, uint32_t size) { struct sb1_inst_t inst; memset(&inst, 0, sizeof(inst)); inst.cmd = SB1_INST_SDRAM; inst.critical = g_critical; inst.sdram.chip_select = cs; inst.sdram.size_index = sb1_sdram_index_by_size(size); if(sb1_sdram_index_by_size(size) < 0) bug("Unknown SDRAM size: %d MB\n", size); if(g_debug) printf("Add instruction: init SDRAM (chip select=%d, size=%d MB)\n", cs, size); return sb1_add_inst(sb, &inst, 1); } static int sb1_add_call(struct sb1_file_t *sb, uint32_t addr, uint32_t arg) { struct sb1_inst_t inst; memset(&inst, 0, sizeof(inst)); inst.cmd = SB1_INST_CALL; inst.critical = g_critical; inst.addr = addr; inst.argument = arg; if(g_debug) printf("Add instruction: call %#x with argument %#x\n", addr, arg); return sb1_add_inst(sb, &inst, 1); } static int sb1_add_jump(struct sb1_file_t *sb, uint32_t addr, uint32_t arg) { struct sb1_inst_t inst; memset(&inst, 0, sizeof(inst)); inst.cmd = SB1_INST_JUMP; inst.critical = g_critical; inst.addr = addr; inst.argument = arg; if(g_debug) printf("Add instruction: jump %#x with argument %#x\n", addr, arg); g_final = true; return sb1_add_inst(sb, &inst, 1); } static int sb1_add_fill(struct sb1_file_t *sb, uint32_t pattern, uint32_t size, uint32_t addr) { while(size > 0) { int len = MIN(size, SB1_CMD_MAX_FILL_SIZE); struct sb1_inst_t inst; memset(&inst, 0, sizeof(inst)); inst.cmd = SB1_INST_FILL; inst.critical = g_critical; inst.size = len; inst.addr = addr; inst.pattern = pattern; inst.datatype = SB1_DATATYPE_UINT32; if(g_debug) printf("Add instruction: fill %#x bytes with pattern %#x at address %#x\n", size, pattern, addr); int ret = sb1_add_inst(sb, &inst, 1); if(ret < 0) return ret; size -= len; addr += len; } return 0; } /** * SB file modification */ static void generate_default_sb_version(struct sb1_version_t *ver) { ver->major = ver->minor = ver->revision = 0x9999; } static struct sb1_file_t *create_sb1_file(void) { struct sb1_file_t *sb = xmalloc(sizeof(struct sb1_file_t)); memset(sb, 0, sizeof(struct sb1_file_t)); /* default versions and key, apply_args() will overwrite if specified */ generate_default_sb_version(&sb->product_ver); generate_default_sb_version(&sb->component_ver); sb1_get_default_key(&sb->key); return sb; } static void *load_file(const char *filename, int *size) { FILE *fd = fopen(filename, "rb"); if(fd == NULL) bug("cannot open '%s' for reading\n", filename); if(g_debug) printf("Loading binary file '%s'...\n", filename); fseek(fd, 0, SEEK_END); *size = ftell(fd); fseek(fd, 0, SEEK_SET); void *data = xmalloc(*size); fread(data, 1, *size, fd); fclose(fd); return data; } static bool parse_sb_sub_version(uint16_t *ver, char *str) { *ver = 0; for(int i = 0; str[i]; i++) { if(i >= 4) return false; if(str[i] < '0' || str[i] > '9') return false; *ver = *ver << 4 | (str[i] - '0'); } return true; } static bool parse_sb_version(struct sb1_version_t *ver, char *str) { char *p = strchr(str, '.'); char *q = strchr(p + 1, '.'); if(p == NULL || q == NULL) return false; *p = *q = 0; return parse_sb_sub_version(&ver->major, str) && parse_sb_sub_version(&ver->minor, p + 1) && parse_sb_sub_version(&ver->revision, q + 1); } /** * Command line parsing */ #define MAX_NR_ARGS 2 #define ARG_STR 0 #define ARG_UINT 1 #define CMD_FN(name) \ int name(struct sb1_file_t *sb, union cmd_arg_t args[MAX_NR_ARGS]) union cmd_arg_t { char *str; unsigned long uint; }; typedef int (*process_arg_t)(struct sb1_file_t *sb, union cmd_arg_t args[MAX_NR_ARGS]); struct cmd_entry_t { const char *name; int nr_args; int arg_type[MAX_NR_ARGS]; process_arg_t fn; }; /* Callbacks */ static void usage(void); CMD_FN(cmd_help) { (void) args; (void) sb; usage(); return 0; } CMD_FN(cmd_debug) { (void) args; (void) sb; g_debug = true; return 0; } CMD_FN(cmd_drive_tag) { sb->drive_tag = args[0].uint; return 0; } CMD_FN(cmd_load_binary) { int size; void *data = load_file(args[0].str, &size); int ret = sb1_add_load(sb, data, size, args[1].uint); free(data); return ret; } CMD_FN(cmd_output) { (void) sb; g_output_file = strdup(args[0].str); return 0; } CMD_FN(cmd_switch) { return sb1_add_switch(sb, args[0].uint); } CMD_FN(cmd_sdram) { return sb1_add_sdram(sb, args[0].uint, args[1].uint); } CMD_FN(cmd_critical) { (void) sb; (void) args; g_critical = true; return 0; } CMD_FN(cmd_clear_critical) { (void) sb; (void) args; g_critical = false; return 0; } CMD_FN(cmd_strict) { (void) sb; (void) args; g_strict = true; return 0; } CMD_FN(cmd_clear_strict) { (void) sb; (void) args; g_strict = false; return 0; } CMD_FN(cmd_call) { /* FIXME: the proprietary sbtoelf always sets argument to 0 ?! */ return sb1_add_call(sb, args[0].uint, g_jump_arg); } CMD_FN(cmd_jump) { return sb1_add_jump(sb, args[0].uint, g_jump_arg); } CMD_FN(cmd_jumparg) { (void) sb; g_jump_arg = args[0].uint; return 0; } static int load_elf(struct sb1_file_t *sb, const char *filename, int act) { struct elf_params_t elf; FILE *fd = fopen(filename, "rb"); if(fd == NULL) bug("cannot open '%s'\n", filename); if(g_debug) printf("Loading elf file '%s'...\n", filename); elf_init(&elf); bool loaded = elf_read_file(&elf, elf_std_read, generic_std_printf, fd); fclose(fd); if(!loaded) bug("error loading elf file '%s'\n", filename); elf_sort_by_address(&elf); struct elf_section_t *esec = elf.first_section; while(esec) { if(esec->type == EST_LOAD) sb1_add_load(sb, esec->section, esec->size, esec->addr); else if(esec->type == EST_FILL) sb1_add_fill(sb, esec->pattern, esec->size, esec->addr); esec = esec->next; } int ret = 0; if(act == SB1_INST_JUMP || act == SB1_INST_CALL) { if(!elf.has_start_addr) bug("Cannot jump/call: '%s' has no start address!\n", filename); if(act == SB1_INST_JUMP) ret = sb1_add_jump(sb, elf.start_addr, g_jump_arg); else ret = sb1_add_call(sb, elf.start_addr, g_jump_arg); } elf_release(&elf); return ret; } CMD_FN(cmd_load) { return load_elf(sb, args[0].str, SB1_INST_LOAD); } CMD_FN(cmd_loadjump) { return load_elf(sb, args[0].str, SB1_INST_JUMP); } CMD_FN(cmd_loadjumpreturn) { return load_elf(sb, args[0].str, SB1_INST_CALL); } CMD_FN(cmd_rom) { sb->rom_version = args[0].uint; return 0; } CMD_FN(cmd_product) { if(!parse_sb_version(&sb->product_ver, args[0].str)) bug("Invalid version string '%s'\n", args[0].str); return 0; } CMD_FN(cmd_component) { if(!parse_sb_version(&sb->component_ver, args[0].str)) bug("Invalid version string '%s'\n", args[0].str); return 0; } CMD_FN(cmd_keyfile) { if(!add_keys_from_file(args[0].str)) bug("Cannot add keys from file '%s'\n", args[0].str); for(int i = 0; i < g_nr_keys; i++) if(g_key_array[i].method == CRYPTO_XOR_KEY) { memcpy(&sb->key, &g_key_array[i], sizeof(sb->key)); break; } return 0; } #define CMD(name,fn,nr_args,...) {name,nr_args,{__VA_ARGS__},fn}, struct cmd_entry_t g_cmds[] = { CMD("-d", cmd_debug, 0) CMD("-debugon", cmd_debug, 0) CMD("-h", cmd_help, 0) CMD("-?", cmd_help, 0) CMD("-load-binary", cmd_load_binary, 2, ARG_STR, ARG_UINT) CMD("-drive-tag", cmd_drive_tag, 1, ARG_UINT) CMD("-o", cmd_output, 1, ARG_STR) CMD("-w", cmd_switch, 1, ARG_UINT) CMD("-switchdriver", cmd_switch, 1, ARG_UINT) CMD("-sdram", cmd_sdram, 2, ARG_UINT, ARG_UINT) CMD("-c", cmd_critical, 0) CMD("-critical", cmd_critical, 0) CMD("-C", cmd_clear_critical, 0) CMD("-noncritical", cmd_clear_critical, 0) CMD("-n", cmd_strict, 0) CMD("-strict", cmd_strict, 0) CMD("-N", cmd_clear_strict, 0) CMD("-nonstrict", cmd_clear_strict, 0) CMD("-call", cmd_call, 1, ARG_UINT) CMD("-jump", cmd_jump, 1, ARG_UINT) CMD("-jumparg", cmd_jumparg, 1, ARG_UINT) CMD("-f", cmd_load, 1, ARG_STR) CMD("-load", cmd_load, 1, ARG_STR) CMD("-r", cmd_loadjumpreturn, 1, ARG_STR) CMD("-loadjumpreturn", cmd_loadjumpreturn, 1, ARG_STR) CMD("-j", cmd_loadjump, 1, ARG_STR) CMD("-loadjump", cmd_loadjump, 1, ARG_STR) CMD("-R", cmd_rom, 1, ARG_UINT) CMD("-rom", cmd_rom, 1, ARG_UINT) CMD("-p", cmd_product, 1, ARG_STR) CMD("-product", cmd_product, 1, ARG_STR) CMD("-v", cmd_component, 1, ARG_STR) CMD("-component", cmd_component, 1, ARG_STR) CMD("-k", cmd_keyfile, 1, ARG_STR) }; #undef CMD #define NR_CMDS (int)(sizeof(g_cmds) / sizeof(g_cmds[0])) static int apply_args(struct sb1_file_t *sb, get_next_arg_t next, void *user) { while(true) { /* next command ? */ char *cmd = next(user); if(cmd == NULL) break; /* switch */ int i = 0; while(i < NR_CMDS && strcmp(cmd, g_cmds[i].name) != 0) i++; if(i == NR_CMDS) bug("Unknown option '%s'\n", cmd); union cmd_arg_t args[MAX_NR_ARGS]; for(int j = 0; j < g_cmds[i].nr_args; j++) { args[j].str = next(user); if(args[j].str == NULL) bug("Option '%s' requires %d arguments, only %d given\n", cmd, g_cmds[i].nr_args, j); if(g_cmds[i].arg_type[j] == ARG_UINT) { char *end; args[j].uint = strtoul(args[j].str, &end, 0); if(*end) bug("Option '%s' expects an integer as argument %d\n", cmd, j + 1); } } int ret = g_cmds[i].fn(sb, args); if(ret < 0) return ret; } return 0; } static void usage(void) { printf("Usage: elftosb1 [options]\n"); printf("Options:\n"); printf(" -h/-?/-help\t\t\tDisplay this message\n"); printf(" -o \t\t\tSet output file\n"); printf(" -d/-debugon\t\t\tEnable debug output\n"); printf(" -k \t\t\tSet key file\n"); printf(" -load-binary \tLoad a binary file at a specified address\n"); printf(" -drive-tag \t\tSpecify drive tag\n"); printf(" -w/-switchdriver \tSwitch driver\n"); printf(" -sdram \tInit SDRAM\n"); printf(" -jumparg \t\tSet jumpparg for jump and jumpreturn\n"); printf(" -f/-load \t\tLoad a ELF file\n"); printf(" -r/-loadjumpreturn \tLoad a ELF file and call it\n"); printf(" -j/-loadjump \t\tLoad a ELF file and jump to it\n"); printf(" -R/-rom \t\tSet ROM version\n"); printf(" -p/-product \t\tSet product version (xxx.xxx.xxx)\n"); printf(" -v/-component \t\tSet component version (xxx.xxx.xxx)\n"); printf(" -c/-critical\t\t\tSet critical flag\n"); printf(" -C/-noncritical\t\tClear critical flag\n"); printf(" -n/-strict\t\t\tSet strict flag\n"); printf(" -N/-nonstrict\t\t\tClear strict flag\n"); printf(" -call \t\t\tCall code at a specified address\n"); printf(" -jump \t\t\tJump to code at a specified address\n"); exit(1); } int main(int argc, char **argv) { if(argc <= 1) usage(); struct sb1_file_t *sb = create_sb1_file(); struct cmd_line_next_arg_user_t u; u.argc = argc - 1; u.argv = argv + 1; int ret = apply_args(sb, &cmd_line_next_arg, &u); if(ret < 0) { sb1_free(sb); return ret; } if(!g_output_file) bug("You must specify an output file\n"); if(!g_final) { if(g_strict) bug("There is no final command in this command stream!\n"); else printf("Warning: there is no final command in this command stream!\n"); } enum sb1_error_t err = sb1_write_file(sb, g_output_file); if(err != SB1_SUCCESS) printf("Error: %d\n", err); return ret; }