| // |
| // Copyright (C) 2015 Marcus Comstedt <marcus@mc.pp.se> |
| // |
| // Permission to use, copy, modify, and/or distribute this software for any |
| // purpose with or without fee is hereby granted, provided that the above |
| // copyright notice and this permission notice appear in all copies. |
| // |
| // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
| // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
| // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| // |
| |
| #include <fstream> |
| #include <iostream> |
| #include <cstdint> |
| #include <memory> |
| |
| #include <getopt.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #ifdef __EMSCRIPTEN__ |
| #include <emscripten.h> |
| #endif |
| |
| #define log(...) fprintf(stderr, __VA_ARGS__); |
| #define error(...) do { fprintf(stderr, "%s: ", program_short_name); fprintf(stderr, __VA_ARGS__); exit(EXIT_FAILURE); } while (0) |
| |
| static char *program_short_name; |
| |
| static const int NUM_IMAGES = 4; |
| static const int HEADER_SIZE = 32; |
| |
| static void align_offset(uint32_t &offset, int bits) |
| { |
| uint32_t mask = (1 << bits) - 1; |
| if (offset & mask) |
| offset = (offset | mask) + 1; |
| } |
| |
| static void write_byte(std::ostream &ofs, uint32_t &file_offset, uint8_t byte) |
| { |
| ofs << byte; |
| file_offset++; |
| } |
| |
| static void write_bytes(std::ostream &ofs, uint32_t &file_offset, |
| const uint8_t *buf, size_t n) |
| { |
| if (n > 0) { |
| ofs.write(reinterpret_cast<const char*>(buf), n); |
| file_offset += n; |
| } |
| } |
| |
| static void write_file(std::ostream &ofs, uint32_t &file_offset, |
| std::istream &ifs, const char *filename) |
| { |
| const size_t bufsize = 8192; |
| uint8_t *buffer = new uint8_t[bufsize]; |
| |
| while(!ifs.eof()) { |
| ifs.read(reinterpret_cast<char *>(buffer), bufsize); |
| if (ifs.bad()) |
| error("can't read input image `%s': %s\n", filename, strerror(errno)); |
| write_bytes(ofs, file_offset, buffer, ifs.gcount()); |
| } |
| |
| delete[] buffer; |
| } |
| |
| static void pad_to(std::ostream &ofs, uint32_t &file_offset, uint32_t target) |
| { |
| if (target < file_offset) |
| error("Trying to pad backwards!\n"); |
| while(file_offset < target) |
| write_byte(ofs, file_offset, 0xff); |
| } |
| |
| class Image { |
| std::ifstream ifs; |
| uint32_t offs; |
| |
| public: |
| const char *const filename; |
| |
| Image(const char *filename); |
| size_t size(); |
| void write(std::ostream &ofs, uint32_t &file_offset); |
| void place(uint32_t o) { offs = o; } |
| uint32_t offset() const { return offs; } |
| }; |
| |
| Image::Image(const char *filename) : ifs(filename, std::ifstream::binary), filename(filename) |
| { |
| if (ifs.fail()) |
| error("can't open input image `%s': %s\n", filename, strerror(errno)); |
| } |
| |
| size_t Image::size() |
| { |
| ifs.seekg (0, ifs.end); |
| if (ifs.fail()) |
| error("can't seek on input image `%s': %s\n", filename, strerror(errno)); |
| size_t length = ifs.tellg(); |
| ifs.seekg (0, ifs.beg); |
| if (ifs.fail()) |
| error("can't seek on input image `%s': %s\n", filename, strerror(errno)); |
| |
| if (length == 0) |
| error("input image `%s' doesn't contain any data\n", filename); |
| return length; |
| } |
| |
| void Image::write(std::ostream &ofs, uint32_t &file_offset) |
| { |
| write_file(ofs, file_offset, ifs, filename); |
| } |
| |
| static void write_header(std::ostream &ofs, uint32_t &file_offset, |
| Image const *image, bool coldboot) |
| { |
| // Preamble |
| write_byte(ofs, file_offset, 0x7e); |
| write_byte(ofs, file_offset, 0xaa); |
| write_byte(ofs, file_offset, 0x99); |
| write_byte(ofs, file_offset, 0x7e); |
| |
| // Boot mode |
| write_byte(ofs, file_offset, 0x92); |
| write_byte(ofs, file_offset, 0x00); |
| write_byte(ofs, file_offset, coldboot ? 0x10 : 0x00); |
| |
| // Boot address |
| write_byte(ofs, file_offset, 0x44); |
| write_byte(ofs, file_offset, 0x03); |
| write_byte(ofs, file_offset, (image->offset() >> 16) & 0xff); |
| write_byte(ofs, file_offset, (image->offset() >> 8) & 0xff); |
| write_byte(ofs, file_offset, image->offset() & 0xff); |
| |
| // Bank offset |
| write_byte(ofs, file_offset, 0x82); |
| write_byte(ofs, file_offset, 0x00); |
| write_byte(ofs, file_offset, 0x00); |
| |
| // Reboot |
| write_byte(ofs, file_offset, 0x01); |
| write_byte(ofs, file_offset, 0x08); |
| |
| // Zero out any unused bytes |
| while (file_offset & (HEADER_SIZE - 1)) |
| write_byte(ofs, file_offset, 0x00); |
| } |
| |
| void usage() |
| { |
| log("\n"); |
| log("Usage: icemulti [options] input-files\n"); |
| log("\n"); |
| log(" -c\n"); |
| log(" coldboot mode, power on reset image is selected by CBSEL0/CBSEL1\n"); |
| log("\n"); |
| log(" -p0, -p1, -p2, -p3\n"); |
| log(" select power on reset image when not using coldboot mode\n"); |
| log("\n"); |
| log(" -a<n>, -A<n>\n"); |
| log(" align images at 2^<n> bytes. -A also aligns image 0.\n"); |
| log("\n"); |
| log(" -o filename\n"); |
| log(" write output image to file instead of stdout\n"); |
| log("\n"); |
| log(" -v\n"); |
| log(" verbose (repeat to increase verbosity)\n"); |
| log("\n"); |
| exit(EXIT_FAILURE); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| #ifdef __EMSCRIPTEN__ |
| EM_ASM( |
| if (ENVIRONMENT_IS_NODE) |
| { |
| FS.mkdir('/hostcwd'); |
| FS.mount(NODEFS, { root: '.' }, '/hostcwd'); |
| FS.mkdir('/hostfs'); |
| FS.mount(NODEFS, { root: '/' }, '/hostfs'); |
| } |
| ); |
| #endif |
| |
| int c; |
| char *endptr = NULL; |
| bool coldboot = false; |
| int por_image = 0; |
| int header_count = 0; |
| int image_count = 0; |
| int align_bits = 0; |
| bool align_first = false; |
| Image *header_images[NUM_IMAGES]; |
| std::unique_ptr<Image> images[NUM_IMAGES]; |
| const char *outfile_name = NULL; |
| bool print_offsets = false; |
| |
| static struct option long_options[] = { |
| {NULL, 0, NULL, 0} |
| }; |
| |
| program_short_name = strrchr(argv[0], '/'); |
| if (program_short_name == NULL) |
| program_short_name = argv[0]; |
| else |
| program_short_name++; |
| |
| while ((c = getopt_long(argc, argv, "cp:a:A:o:v", |
| long_options, NULL)) != -1) |
| switch (c) { |
| case 'c': |
| coldboot = true; |
| break; |
| case 'p': |
| if (optarg[0] == '0' && optarg[1] == '\0') |
| por_image = 0; |
| else if (optarg[0] == '1' && optarg[1] == '\0') |
| por_image = 1; |
| else if (optarg[0] == '2' && optarg[1] == '\0') |
| por_image = 2; |
| else if (optarg[0] == '3' && optarg[1] == '\0') |
| por_image = 3; |
| else |
| error("`%s' is not a valid power-on/reset image (must be 0, 1, 2, or 3)\n", optarg); |
| break; |
| case 'A': |
| align_first = true; |
| /* fallthrough */ |
| case 'a': |
| align_bits = strtol(optarg, &endptr, 0); |
| if (*endptr != '\0') |
| error("`%s' is not a valid number\n", optarg); |
| if (align_bits < 0) |
| error("argument to `-%c' must be non-negative\n", c); |
| break; |
| case 'o': |
| outfile_name = optarg; |
| break; |
| case 'v': |
| print_offsets = true; |
| break; |
| default: |
| usage(); |
| } |
| |
| if (optind == argc) { |
| fprintf(stderr, "%s: missing argument\n", program_short_name); |
| usage(); |
| } |
| |
| while (optind != argc) { |
| if (header_count >= NUM_IMAGES) |
| error("Too many images supplied\n"); |
| for (int i = 0; i < image_count; i++) |
| if (strcmp(argv[optind], images[i]->filename) == 0) { |
| header_images[header_count] = &*images[i]; |
| goto image_found; |
| } |
| images[image_count].reset(new Image(argv[optind])); |
| header_images[header_count] = &*images[image_count]; |
| image_count++; |
| |
| image_found: |
| header_count++; |
| optind++; |
| } |
| |
| if (coldboot && por_image != 0) |
| error("Can't select power on reset boot image in cold boot mode\n"); |
| |
| if (por_image >= header_count) |
| error("Specified non-existing image for power on reset\n"); |
| |
| // Place images |
| uint32_t offs = (NUM_IMAGES + 1) * HEADER_SIZE; |
| if (align_first) |
| align_offset(offs, align_bits); |
| for (int i=0; i<image_count; i++) { |
| images[i]->place(offs); |
| offs += images[i]->size(); |
| align_offset(offs, align_bits); |
| if (print_offsets) |
| fprintf(stderr, "Place image %d at %06x .. %06x (`%s')\n", i, int(images[i]->offset()), int(offs), images[i]->filename); |
| } |
| |
| // Populate headers |
| for (int i=header_count; i < NUM_IMAGES; i++) |
| header_images[i] = header_images[por_image]; |
| |
| std::ofstream ofs; |
| std::ostream *osp; |
| |
| if (outfile_name != NULL) { |
| ofs.open(outfile_name, std::ofstream::binary); |
| if (!ofs.is_open()) |
| error("can't open output file `%s': %s\n", outfile_name, strerror(errno)); |
| osp = &ofs; |
| } else { |
| osp = &std::cout; |
| } |
| |
| uint32_t file_offset = 0; |
| for (int i=0; i<NUM_IMAGES + 1; i++) |
| { |
| pad_to(*osp, file_offset, i * HEADER_SIZE); |
| if (i == 0) |
| write_header(*osp, file_offset, header_images[por_image], coldboot); |
| else |
| write_header(*osp, file_offset, header_images[i - 1], false); |
| } |
| for (int i=0; i<image_count; i++) |
| { |
| pad_to(*osp, file_offset, images[i]->offset()); |
| images[i]->write(*osp, file_offset); |
| } |
| |
| return EXIT_SUCCESS; |
| } |