The Marvell EBU SoCs (Kirkwood, Armada 370, Armada XP) have a BootROM that understand a specific image format, composed of a main header, several extension headers and a paylod. This image can be booted from NAND, SPI, SATA, UART, NOR, etc. This patch adds a tool that allows to extract the components and configuration of existing images, and to create new images. Signed-off-by: Thomas Petazzoni <thomas.petazzoni@xxxxxxxxxxxxxxxxxx> --- scripts/.gitignore | 1 + scripts/Makefile | 3 +- scripts/kwbimage.c | 1224 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1227 insertions(+), 1 deletion(-) create mode 100644 scripts/kwbimage.c diff --git a/scripts/.gitignore b/scripts/.gitignore index 1ca6603..414de7f 100644 --- a/scripts/.gitignore +++ b/scripts/.gitignore @@ -2,6 +2,7 @@ bareboxenv bin2c gen_netx_image kallsyms +kwbimage mk-am35xx-spi-image mkimage mkublheader diff --git a/scripts/Makefile b/scripts/Makefile index 08b325c..22ec4b6 100644 --- a/scripts/Makefile +++ b/scripts/Makefile @@ -8,6 +8,7 @@ hostprogs-$(CONFIG_KALLSYMS) += kallsyms hostprogs-y += bin2c hostprogs-y += mkimage hostprogs-y += bareboxenv +hostprogs-$(CONFIG_ARCH_MVEBU) += kwbimage hostprogs-$(CONFIG_ARCH_NETX) += gen_netx_image hostprogs-$(CONFIG_ARCH_OMAP) += omap_signGP mk-am35xx-spi-image hostprogs-$(CONFIG_ARCH_S5PCxx) += s5p_cksum @@ -27,7 +28,7 @@ subdir-$(CONFIG_X86) += setupmbr subdir- += basic kconfig setupmbr quiet_cmd_csingle = CC $@ - cmd_csingle = $(CC) -Wp,-MD,$(depfile) $(CFLAGS) -o $@ $< + cmd_csingle = $(CC) -Wp,-MD,$(depfile) $(CFLAGS) -o $@ $< obj-$(CONFIG_BAREBOXENV_TARGET) += bareboxenv-target diff --git a/scripts/kwbimage.c b/scripts/kwbimage.c new file mode 100644 index 0000000..864114d --- /dev/null +++ b/scripts/kwbimage.c @@ -0,0 +1,1224 @@ +/* + * Image manipulator for Kirkwood, Armada 370 and Armada XP platforms. + * + * (C) Copyright 2013 Thomas Petazzoni + * <thomas.petazzoni@xxxxxxxxxxxxxxxxxx> + * + * 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 program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This tool allows to extract and create bootable images for Marvell + * Kirkwood, Marvell Armada 370 and Armada XP SoCs. It supports two + * versions of the bootable image format: version 0 (used on Marvell + * Kirkwood) and version 1 (used on Marvell Armada 370/XP). + * + * To extract an image, run: + * ./scripts/kwbimage -x -i <image-file> -o <some-directory> + * + * In <some-directory>, kwbimage will output 'kwbimage.cfg', the + * configuration file that describes the image, 'payload', which is + * the bootloader code itself, and it may output a 'binary.0' file + * that corresponds to a binary blob (only possible in version 1 + * images). + * + * To create an image, run: + * ./scripts/kwbimage -x -c <image-cfg-file> -o <image-file> + * + * The given configuration file is in the format of the 'kwbimage.cfg' + * file, and should reference the payload file (generally the + * bootloader code) and optionally a binary blob. + * + * Not implemented: support for the register headers and secure + * headers in v1 images + */ + +#define _GNU_SOURCE +#include <stdio.h> +#include <stdint.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/mman.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> + +#define ALIGN_SUP(x, a) (((x) + (a - 1)) & ~(a - 1)) + +/* Structure of the main header, version 0 (Kirkwood) */ +struct main_hdr_v0 { + uint8_t blockid; /*0 */ + uint8_t nandeccmode; /*1 */ + uint16_t nandpagesize; /*2-3 */ + uint32_t blocksize; /*4-7 */ + uint32_t rsvd1; /*8-11 */ + uint32_t srcaddr; /*12-15 */ + uint32_t destaddr; /*16-19 */ + uint32_t execaddr; /*20-23 */ + uint8_t satapiomode; /*24 */ + uint8_t rsvd3; /*25 */ + uint16_t ddrinitdelay; /*26-27 */ + uint16_t rsvd2; /*28-29 */ + uint8_t ext; /*30 */ + uint8_t checksum; /*31 */ +}; + +struct ext_hdr_v0_reg { + uint32_t raddr; + uint32_t rdata; +}; + +#define EXT_HDR_V0_REG_COUNT ((0x1dc - 0x20)/sizeof(struct ext_hdr_v0_reg)) + +struct ext_hdr_v0 { + uint32_t offset; + uint8_t reserved[0x20 - sizeof(uint32_t)]; + struct ext_hdr_v0_reg rcfg[EXT_HDR_V0_REG_COUNT]; + uint8_t reserved2[7]; + uint8_t checksum; +}; + +/* Structure of the main header, version 1 (Armada 370, Armada XP) */ +struct main_hdr_v1 { + uint8_t blockid; /* 0 */ + uint8_t reserved1; /* 1 */ + uint16_t reserved2; /* 2-3 */ + uint32_t blocksize; /* 4-7 */ + uint8_t version; /* 8 */ + uint8_t headersz_msb; /* 9 */ + uint16_t headersz_lsb; /* A-B */ + uint32_t srcaddr; /* C-F */ + uint32_t destaddr; /* 10-13 */ + uint32_t execaddr; /* 14-17 */ + uint8_t reserved3; /* 18 */ + uint8_t nandblocksize; /* 19 */ + uint8_t nandbadblklocation; /* 1A */ + uint8_t reserved4; /* 1B */ + uint16_t reserved5; /* 1C-1D */ + uint8_t ext; /* 1E */ + uint8_t checksum; /* 1F */ +}; + +/* + * Header for the optional headers, version 1 (Armada 370, Armada XP) + */ +struct opt_hdr_v1 { + uint8_t headertype; + uint8_t headersz_msb; + uint16_t headersz_lsb; + char data[0]; +}; + +/* + * Various values for the opt_hdr_v1->headertype field, describing the + * different types of optional headers. The "secure" header contains + * informations related to secure boot (encryption keys, etc.). The + * "binary" header contains ARM binary code to be executed prior to + * executing the main payload (usually the bootloader). This is + * typically used to execute DDR3 training code. The "register" header + * allows to describe a set of (address, value) tuples that are + * generally used to configure the DRAM controller. + */ +#define OPT_HDR_V1_SECURE_TYPE 0x1 +#define OPT_HDR_V1_BINARY_TYPE 0x2 +#define OPT_HDR_V1_REGISTER_TYPE 0x3 + +#define KWBHEADER_V1_SIZE(hdr) \ + (((hdr)->headersz_msb << 16) | (hdr)->headersz_lsb) + +struct boot_mode { + unsigned int id; + const char *name; +}; + +struct boot_mode boot_modes[] = { + { 0x4D, "i2c" }, + { 0x5A, "spi" }, + { 0x8B, "nand" }, + { 0x78, "sata" }, + { 0x9C, "pex" }, + { 0x69, "uart" }, + {}, +}; + +struct nand_ecc_mode { + unsigned int id; + const char *name; +}; + +struct nand_ecc_mode nand_ecc_modes[] = { + { 0x00, "default" }, + { 0x01, "hamming" }, + { 0x02, "rs" }, + { 0x03, "disabled" }, + {}, +}; + +#define BINARY_MAX_ARGS 8 + +/* In-memory representation of a line of the configuration file */ +struct image_cfg_element { + enum { + IMAGE_CFG_VERSION = 0x1, + IMAGE_CFG_BOOT_FROM, + IMAGE_CFG_DEST_ADDR, + IMAGE_CFG_EXEC_ADDR, + IMAGE_CFG_NAND_BLKSZ, + IMAGE_CFG_NAND_BADBLK_LOCATION, + IMAGE_CFG_BINARY, + IMAGE_CFG_PAYLOAD, + IMAGE_CFG_DATA, + } type; + union { + unsigned int version; + const char *boot_from; + struct { + const char *file; + unsigned int args[BINARY_MAX_ARGS]; + unsigned int nargs; + } binary; + const char *payload; + unsigned int dstaddr; + unsigned int execaddr; + unsigned int nandblksz; + unsigned int nandbadblklocation; + struct ext_hdr_v0_reg regdata; + }; +}; + +/* + * Byte 8 of the image header contains the version number. In the v0 + * header, byte 8 was reserved, and always set to 0. In the v1 header, + * byte 8 has been changed to a proper field, set to 1. + */ +static unsigned int image_version(void *header) +{ + unsigned char *ptr = header; + return ptr[8]; +} + +/* + * Utility functions to manipulate boot mode and ecc modes (convert + * them back and forth between description strings and the + * corresponding numerical identifiers). + */ + +static const char *image_boot_mode_name(unsigned int id) +{ + int i; + for (i = 0; boot_modes[i].name; i++) + if (boot_modes[i].id == id) + return boot_modes[i].name; + return NULL; +} + +unsigned int image_boot_mode_id(const char *boot_mode_name) +{ + int i; + for (i = 0; boot_modes[i].name; i++) + if (!strcmp(boot_modes[i].name, boot_mode_name)) + return boot_modes[i].id; + + return 0; +} + +static const char *image_nand_ecc_mode_name(unsigned int id) +{ + int i; + for (i = 0; nand_ecc_modes[i].name; i++) + if (nand_ecc_modes[i].id == id) + return nand_ecc_modes[i].name; + return NULL; +} + +/* + * Compute a 8-bit checksum of a memory area. This algorithm follows + * the requirements of the Marvell SoC BootROM specifications. + */ +static uint8_t image_checksum8(void *start, uint32_t len) +{ + uint8_t csum = 0; + uint8_t *p = start; + + /* check len and return zero checksum if invalid */ + if (!len) + return 0; + + do { + csum += *p; + p++; + } while (--len); + + return csum; +} + +static uint32_t image_checksum32 (void *start, uint32_t len) +{ + uint32_t csum = 0; + uint32_t *p = start; + + /* check len and return zero checksum if invalid */ + if (!len) + return 0; + + if (len % sizeof(uint32_t)) { + fprintf (stderr, "Length %d is not in multiple of %zu\n", + len, sizeof(uint32_t)); + return 0; + } + + do { + csum += *p; + p++; + len -= sizeof(uint32_t); + } while (len > 0); + + return csum; +} + +static void usage(const char *prog) +{ + printf("Usage: %s [-c | -x] -i <input> -o <output>\n", prog); + printf(" -c: create a new image\n"); + printf(" -x: extract an existing image\n"); + printf(" -i: input file\n"); + printf(" when used with -c, should point to a kwbimage.cfg file\n"); + printf(" when used with -x, should point to the image to be extracted\n"); + printf(" -o: output file/directory\n"); + printf(" when used with -c, should point to the image file to create\n"); + printf(" when used with -x, should point to a directory when the image will be extracted\n"); +} + +static int image_extract_payload(void *payload, size_t sz, const char *output) +{ + char *imageoutname; + FILE *imageout; + int ret; + + ret = asprintf(&imageoutname, "%s/payload", output); + if (ret < 0) { + fprintf(stderr, "Cannot allocate memory\n"); + return -1; + } + + imageout = fopen(imageoutname, "w+"); + if (!imageout) { + fprintf(stderr, "Could not open output file %s\n", + imageoutname); + free(imageoutname); + return -1; + } + + ret = fwrite(payload, sz, 1, imageout); + if (ret != 1) { + fprintf(stderr, "Could not write to open file %s\n", + imageoutname); + fclose(imageout); + free(imageoutname); + return -1; + } + + fclose(imageout); + free(imageoutname); + return 0; +} + +static int image_extract_v0(void *fdimap, const char *output, FILE *focfg) +{ + struct main_hdr_v0 *main_hdr = fdimap; + struct ext_hdr_v0 *ext_hdr; + const char *boot_mode_name; + uint32_t *img_checksum; + size_t payloadsz; + int cksum, i; + + /* + * Verify checksum. When calculating the header, discard the + * last byte of the header, which itself contains the + * checksum. + */ + cksum = image_checksum8(main_hdr, sizeof(struct main_hdr_v0)-1); + if (cksum != main_hdr->checksum) { + fprintf(stderr, + "Invalid main header checksum: 0x%08x vs. 0x%08x\n", + cksum, main_hdr->checksum); + return -1; + } + + boot_mode_name = image_boot_mode_name(main_hdr->blockid); + if (!boot_mode_name) { + fprintf(stderr, "Invalid boot ID: 0x%x\n", + main_hdr->blockid); + return -1; + } + + fprintf(focfg, "VERSION 0\n"); + fprintf(focfg, "BOOT_FROM %s\n", boot_mode_name); + fprintf(focfg, "DESTADDR %08x\n", main_hdr->destaddr); + fprintf(focfg, "EXECADDR %08x\n", main_hdr->execaddr); + + if (!strcmp(boot_mode_name, "nand")) { + const char *nand_ecc_mode = + image_nand_ecc_mode_name(main_hdr->nandeccmode); + fprintf(focfg, "NAND_ECC_MODE %s\n", + nand_ecc_mode); + fprintf(focfg, "NAND_PAGE_SIZE %08x\n", + main_hdr->nandpagesize); + } + + /* No extension header, we're done */ + if (!main_hdr->ext) + return 0; + + ext_hdr = fdimap + sizeof(struct main_hdr_v0); + + for (i = 0; i < EXT_HDR_V0_REG_COUNT; i++) { + if (ext_hdr->rcfg[i].raddr == 0 && + ext_hdr->rcfg[i].rdata == 0) + break; + + fprintf(focfg, "DATA %08x %08x\n", + ext_hdr->rcfg[i].raddr, + ext_hdr->rcfg[i].rdata); + } + + /* The image is concatenated with a 32 bits checksum */ + payloadsz = main_hdr->blocksize - sizeof(uint32_t); + img_checksum = (uint32_t *) (fdimap + main_hdr->srcaddr + payloadsz); + + if (*img_checksum != image_checksum32(fdimap + main_hdr->srcaddr, + payloadsz)) { + fprintf(stderr, "The image checksum does not match\n"); + return -1; + } + + /* Finally, handle the image itself */ + fprintf(focfg, "PAYLOAD %s/payload\n", output); + return image_extract_payload(fdimap + main_hdr->srcaddr, + payloadsz, output); +} + +static int image_extract_binary_hdr_v1(const void *binary, const char *output, + FILE *focfg, int hdrnum, size_t binsz) +{ + char *binaryoutname; + FILE *binaryout; + const unsigned int *args; + unsigned int nargs; + int ret, i; + + args = binary; + nargs = args[0]; + args++; + + ret = asprintf(&binaryoutname, "%s/binary.%d", output, + hdrnum); + if (ret < 0) { + fprintf(stderr, "Couldn't not allocate memory\n"); + return ret; + } + + binaryout = fopen(binaryoutname, "w+"); + if (!binaryout) { + fprintf(stderr, "Couldn't open output file %s\n", + binaryoutname); + free(binaryoutname); + return -1; + } + + ret = fwrite(binary + (nargs + 1) * sizeof(unsigned int), + binsz - (nargs + 1) * sizeof(unsigned int), 1, + binaryout); + if (ret != 1) { + fprintf(stderr, "Could not write to output file %s\n", + binaryoutname); + fclose(binaryout); + free(binaryoutname); + return -1; + } + + fclose(binaryout); + + fprintf(focfg, "BINARY %s", binaryoutname); + for (i = 0; i < nargs; i++) + fprintf(focfg, " %08x", args[i]); + fprintf(focfg, "\n"); + + free(binaryoutname); + + return 0; +} + +static int image_extract_v1(void *fdimap, const char *output, FILE *focfg) +{ + struct main_hdr_v1 *main_hdr = fdimap; + struct opt_hdr_v1 *opt_hdr; + const char *boot_mode_name; + int headersz = KWBHEADER_V1_SIZE(main_hdr); + int hasheaders; + uint8_t cksum; + int opthdrid; + + /* + * Verify the checkum. We have to substract the checksum + * itself, because when the checksum is calculated, the + * checksum field is 0. + */ + cksum = image_checksum8(main_hdr, headersz); + cksum -= main_hdr->checksum; + + if (cksum != main_hdr->checksum) { + fprintf(stderr, + "Invalid main header checksum: 0x%08x vs. 0x%08x\n", + cksum, main_hdr->checksum); + return -1; + } + + /* First, take care of the main header */ + boot_mode_name = image_boot_mode_name(main_hdr->blockid); + if (!boot_mode_name) { + fprintf(stderr, "Invalid boot ID: 0x%x\n", + main_hdr->blockid); + return -1; + } + + fprintf(focfg, "VERSION 1\n"); + fprintf(focfg, "BOOT_FROM %s\n", boot_mode_name); + fprintf(focfg, "DESTADDR %08x\n", main_hdr->destaddr); + fprintf(focfg, "EXECADDR %08x\n", main_hdr->execaddr); + fprintf(focfg, "NAND_BLKSZ %08x\n", + main_hdr->nandblocksize * 64 * 1024); + fprintf(focfg, "NAND_BADBLK_LOCATION %02x\n", + main_hdr->nandbadblklocation); + + hasheaders = main_hdr->ext; + opt_hdr = fdimap + sizeof(struct main_hdr_v1); + opthdrid = 0; + + /* Then, go through all the extension headers */ + while (hasheaders) { + int opthdrsz = KWBHEADER_V1_SIZE(opt_hdr); + + switch (opt_hdr->headertype) { + case OPT_HDR_V1_BINARY_TYPE: + image_extract_binary_hdr_v1(opt_hdr->data, output, + focfg, opthdrid, + opthdrsz - + sizeof(struct opt_hdr_v1)); + break; + case OPT_HDR_V1_SECURE_TYPE: + case OPT_HDR_V1_REGISTER_TYPE: + fprintf(stderr, + "Support for header type 0x%x not implemented\n", + opt_hdr->headertype); + exit(1); + break; + default: + fprintf(stderr, "Invalid header type 0x%x\n", + opt_hdr->headertype); + exit(1); + } + + /* + * The first byte of the last double word of the + * current header indicates whether there is a next + * header or not. + */ + hasheaders = ((char *)opt_hdr)[opthdrsz - 4]; + + /* Move to the next header */ + opt_hdr = ((void *)opt_hdr) + opthdrsz; + opthdrid++; + } + + /* Finally, handle the image itself */ + fprintf(focfg, "PAYLOAD %s/payload\n", output); + return image_extract_payload(fdimap + main_hdr->srcaddr, + main_hdr->blocksize - 4, + output); +} + +static int image_extract(const char *input, const char *output) +{ + int fdi, ret; + struct stat fdistat, fdostat; + void *fdimap; + char *focfgname; + FILE *focfg; + + fdi = open(input, O_RDONLY); + if (fdi < 0) { + fprintf(stderr, "Cannot open input file %s: %m\n", + input); + return -1; + } + + ret = fstat(fdi, &fdistat); + if (ret < 0) { + fprintf(stderr, "Cannot stat input file %s: %m\n", + input); + close(fdi); + return -1; + } + + fdimap = mmap(NULL, fdistat.st_size, PROT_READ, MAP_PRIVATE, fdi, 0); + if (fdimap == MAP_FAILED) { + fprintf(stderr, "Cannot map input file %s: %m\n", + input); + close(fdi); + return -1; + } + + close(fdi); + + ret = stat(output, &fdostat); + if (ret < 0) { + fprintf(stderr, "Cannot stat output directory %s: %m\n", + output); + munmap(fdimap, fdistat.st_size); + return -1; + } + + if (!S_ISDIR(fdostat.st_mode)) { + fprintf(stderr, "Output %s should be a directory\n", + output); + munmap(fdimap, fdistat.st_size); + return -1; + } + + ret = asprintf(&focfgname, "%s/kwbimage.cfg", output); + if (ret < 0) { + fprintf(stderr, "Failed to allocate memory\n"); + munmap(fdimap, fdistat.st_size); + return -1; + } + + focfg = fopen(focfgname, "w+"); + if (!focfg) { + fprintf(stderr, "Output file %s could not be created\n", + focfgname); + free(focfgname); + munmap(fdimap, fdistat.st_size); + return -1; + } + + free(focfgname); + + if (image_version(fdimap) == 0) + ret = image_extract_v0(fdimap, output, focfg); + else if (image_version(fdimap) == 1) + ret = image_extract_v1(fdimap, output, focfg); + else { + fprintf(stderr, "Invalid image version %d\n", + image_version(fdimap)); + ret = -1; + } + + fclose(focfg); + munmap(fdimap, fdistat.st_size); + return ret; +} + +static int image_create_payload(void *payload_start, size_t payloadsz, + const char *payload_filename) +{ + FILE *payload; + uint32_t *payload_checksum = + (uint32_t *) (payload_start + payloadsz); + int ret; + + payload = fopen(payload_filename, "r"); + if (!payload) { + fprintf(stderr, "Cannot open payload file %s\n", + payload_filename); + return -1; + } + + ret = fread(payload_start, payloadsz, 1, payload); + if (ret != 1) { + fprintf(stderr, "Cannot read payload file %s\n", + payload_filename); + return -1; + } + + fclose(payload); + + *payload_checksum = image_checksum32(payload_start, payloadsz); + return 0; +} + +static void *image_create_v0(struct image_cfg_element *image_cfg, + int cfgn, const char *output, size_t *imagesz) +{ + size_t headersz, payloadsz, totalsz; + struct main_hdr_v0 *main_hdr; + struct ext_hdr_v0 *ext_hdr; + void *image; + int has_ext = 0; + int cfgi, ret; + + /* Calculate the size of the header and the size of the + * payload */ + headersz = sizeof(struct main_hdr_v0); + payloadsz = 0; + for (cfgi = 0; cfgi < cfgn; cfgi++) { + struct image_cfg_element *el = &image_cfg[cfgi]; + if (el->type == IMAGE_CFG_DATA) + has_ext = 1; + else if (el->type == IMAGE_CFG_PAYLOAD) { + struct stat s; + int ret; + + ret = stat(el->payload, &s); + if (ret < 0) { + fprintf(stderr, "Cannot stat payload file %s\n", + el->payload); + return NULL; + } + + payloadsz = s.st_size; + } + } + + if (has_ext) + headersz += sizeof(struct ext_hdr_v0); + + /* Headers, payload and 32-bits checksum */ + totalsz = headersz + payloadsz + sizeof(uint32_t); + + image = malloc(totalsz); + if (!image) { + fprintf(stderr, "Cannot allocate memory for image\n"); + return NULL; + } + + memset(image, 0, totalsz); + + main_hdr = image; + + /* Fill in the main header */ + main_hdr->blocksize = payloadsz + sizeof(uint32_t); + main_hdr->srcaddr = headersz; + main_hdr->ext = has_ext; + for (cfgi = 0; cfgi < cfgn; cfgi++) { + struct image_cfg_element *el = &image_cfg[cfgi]; + if (el->type == IMAGE_CFG_BOOT_FROM) + main_hdr->blockid = image_boot_mode_id(el->boot_from); + else if (el->type == IMAGE_CFG_DEST_ADDR) + main_hdr->destaddr = el->dstaddr; + else if (el->type == IMAGE_CFG_EXEC_ADDR) + main_hdr->execaddr = el->execaddr; + else if (el->type != IMAGE_CFG_VERSION) + break; + } + + main_hdr->checksum = image_checksum8(image, + sizeof(struct main_hdr_v0)); + + /* Generate the ext header */ + if (has_ext) { + int datai = 0; + + ext_hdr = image + sizeof(struct main_hdr_v0); + ext_hdr->offset = 0x40; + + for (; cfgi < cfgn; cfgi++) { + struct image_cfg_element *el = &image_cfg[cfgi]; + if (el->type == IMAGE_CFG_DATA) { + ext_hdr->rcfg[datai].raddr = el->regdata.raddr; + ext_hdr->rcfg[datai].rdata = el->regdata.rdata; + datai++; + } + else if (el->type == IMAGE_CFG_PAYLOAD) + break; + else { + fprintf(stderr, "Invalid element of type %d\n", + el->type); + return NULL; + } + } + + ext_hdr->checksum = image_checksum8(ext_hdr, + sizeof(struct ext_hdr_v0)); + } + + /* Now, time to take care of the payload, if any */ + if (image_cfg[cfgi].type == IMAGE_CFG_PAYLOAD) { + if (cfgi != cfgn - 1) { + fprintf(stderr, + "There should always be only one payload, and it should be the last element\n"); + return NULL; + } + + ret = image_create_payload(image + headersz, payloadsz, + image_cfg[cfgi].payload); + if (ret < 0) + return NULL; + } + + *imagesz = totalsz; + return image; +} + +static void *image_create_v1(struct image_cfg_element *image_cfg, + int cfgn, const char *output, size_t *imagesz) +{ + struct main_hdr_v1 *main_hdr; + size_t headersz, payloadsz, totalsz; + void *image, *cur; + int hasext = 0; + int cfgi, ret; + + /* Calculate the size of the header and the size of the + * payload */ + headersz = sizeof(struct main_hdr_v1); + payloadsz = 0; + for (cfgi = 0; cfgi < cfgn; cfgi++) { + struct image_cfg_element *el = &image_cfg[cfgi]; + if (el->type == IMAGE_CFG_BINARY) { + struct stat s; + + ret = stat(el->binary.file, &s); + if (ret < 0) { + fprintf(stderr, "Cannot stat binary file %s\n", + el->binary.file); + return NULL; + } + + headersz += s.st_size + + el->binary.nargs * sizeof(unsigned int); + hasext = 1; + } else if (el->type == IMAGE_CFG_PAYLOAD) { + struct stat s; + + ret = stat(el->payload, &s); + if (ret < 0) { + fprintf(stderr, "Cannot stat payload file %s\n", + el->payload); + return NULL; + } + + payloadsz = s.st_size; + } + } + + /* The payload should be aligned on some reasonable + * boundary */ + headersz = ALIGN_SUP(headersz, 4096); + + /* The total size includes the headers, the payload, and the + * 32 bits checksum at the end of the payload */ + totalsz = headersz + payloadsz + sizeof(uint32_t); + + image = malloc(totalsz); + if (!image) { + fprintf(stderr, "Cannot allocate memory for image\n"); + return NULL; + } + + memset(image, 0, totalsz); + + cur = main_hdr = image; + cur += sizeof(struct main_hdr_v1); + + main_hdr->blocksize = payloadsz + sizeof(uint32_t); + main_hdr->headersz_lsb = headersz & 0xFFFF; + main_hdr->headersz_msb = (headersz & 0xFFFF0000) >> 16; + main_hdr->srcaddr = headersz; + main_hdr->ext = hasext; + + /* First, take care of filling the main header */ + for (cfgi = 0; cfgi < cfgn; cfgi++) { + struct image_cfg_element *el = &image_cfg[cfgi]; + if (el->type == IMAGE_CFG_VERSION) + main_hdr->version = 1; + else if (el->type == IMAGE_CFG_BOOT_FROM) + main_hdr->blockid = image_boot_mode_id(el->boot_from); + else if (el->type == IMAGE_CFG_DEST_ADDR) + main_hdr->destaddr = el->dstaddr; + else if (el->type == IMAGE_CFG_EXEC_ADDR) + main_hdr->execaddr = el->execaddr; + else if (el->type == IMAGE_CFG_NAND_BLKSZ) + main_hdr->nandblocksize = el->nandblksz / (64 * 1024); + else if (el->type == IMAGE_CFG_NAND_BADBLK_LOCATION) + main_hdr->nandbadblklocation = el->nandbadblklocation; + else + break; + } + + /* Then, fill the extension headers */ + for (; cfgi < cfgn; cfgi++) { + struct image_cfg_element *el = &image_cfg[cfgi]; + + if (el->type == IMAGE_CFG_BINARY) { + struct opt_hdr_v1 *hdr = cur; + unsigned int *args; + size_t binhdrsz; + struct stat s; + int argi; + FILE *bin; + + hdr->headertype = OPT_HDR_V1_BINARY_TYPE; + + bin = fopen(el->binary.file, "r"); + if (!bin) { + fprintf(stderr, "Cannot open binary file %s\n", + el->binary.file); + return NULL; + } + + fstat(fileno(bin), &s); + + binhdrsz = sizeof(struct opt_hdr_v1) + + (el->binary.nargs + 1) * sizeof(unsigned int) + + s.st_size; + hdr->headersz_lsb = binhdrsz & 0xFFFF; + hdr->headersz_msb = (binhdrsz & 0xFFFF0000) >> 16; + + cur += sizeof(struct opt_hdr_v1); + + args = cur; + *args = el->binary.nargs; + args++; + for (argi = 0; argi < el->binary.nargs; argi++) + args[argi] = el->binary.args[argi]; + + cur += (el->binary.nargs + 1) * sizeof(unsigned int); + + ret = fread(cur, s.st_size, 1, bin); + if (ret != 1) { + fprintf(stderr, + "Could not read binary image %s\n", + el->binary.file); + return NULL; + } + + fclose(bin); + + cur += s.st_size; + + /* See if we have a next header or not, and if + * so, add the marker indicating that we are + * not the last header */ + if ((cfgi < (cfgn - 1)) && + (image_cfg[cfgi + 1].type != IMAGE_CFG_PAYLOAD)) + *((unsigned char *) cur) = 1; + cur += sizeof(uint32_t); + } else if (el->type == IMAGE_CFG_PAYLOAD) + break; + else { + fprintf(stderr, "Invalid element type %d (cfgi=%d)\n", + el->type, cfgi); + return NULL; + } + } + + /* Calculate and set the header checksum */ + main_hdr->checksum = image_checksum8(main_hdr, headersz); + + /* Now, time to take care of the payload, if any */ + if (image_cfg[cfgi].type == IMAGE_CFG_PAYLOAD) { + if (cfgi != cfgn - 1) { + fprintf(stderr, + "There should always be only one payload, and it should be the last element\n"); + return NULL; + } + + ret = image_create_payload(image + headersz, payloadsz, + image_cfg[cfgi].payload); + if (ret < 0) + return NULL; + } + + *imagesz = totalsz; + return image; +} + +static int image_create_config_parse_oneline(char *line, + struct image_cfg_element *el) +{ + char *keyword, *saveptr; + + keyword = strtok_r(line, " ", &saveptr); + if (!strcmp(keyword, "VERSION")) { + char *value = strtok_r(NULL, " ", &saveptr); + el->type = IMAGE_CFG_VERSION; + el->version = atoi(value); + } else if (!strcmp(keyword, "BOOT_FROM")) { + char *value = strtok_r(NULL, " ", &saveptr); + el->type = IMAGE_CFG_BOOT_FROM; + el->boot_from = strdup(value); + } else if (!strcmp(keyword, "DESTADDR")) { + char *value = strtok_r(NULL, " ", &saveptr); + el->type = IMAGE_CFG_DEST_ADDR; + el->dstaddr = strtol(value, NULL, 16); + } else if (!strcmp(keyword, "EXECADDR")) { + char *value = strtok_r(NULL, " ", &saveptr); + el->type = IMAGE_CFG_EXEC_ADDR; + el->execaddr = strtol(value, NULL, 16); + } else if (!strcmp(keyword, "NAND_BLKSZ")) { + char *value = strtok_r(NULL, " ", &saveptr); + el->type = IMAGE_CFG_NAND_BLKSZ; + el->nandblksz = strtol(value, NULL, 16); + } else if (!strcmp(keyword, "NAND_BADBLK_LOCATION")) { + char *value = strtok_r(NULL, " ", &saveptr); + el->type = IMAGE_CFG_NAND_BADBLK_LOCATION; + el->nandbadblklocation = + strtol(value, NULL, 16); + } else if (!strcmp(keyword, "BINARY")) { + char *value = strtok_r(NULL, " ", &saveptr); + int argi = 0; + + el->type = IMAGE_CFG_BINARY; + el->binary.file = strdup(value); + while (1) { + value = strtok_r(NULL, " ", &saveptr); + if (!value) + break; + el->binary.args[argi] = strtol(value, NULL, 16); + argi++; + if (argi >= BINARY_MAX_ARGS) { + fprintf(stderr, + "Too many argument for binary\n"); + return -1; + } + } + el->binary.nargs = argi; + } else if (!strcmp(keyword, "DATA")) { + char *value1 = strtok_r(NULL, " ", &saveptr); + char *value2 = strtok_r(NULL, " ", &saveptr); + + if (!value1 || !value2) { + fprintf(stderr, "Invalid number of arguments for DATA\n"); + return -1; + } + + el->type = IMAGE_CFG_DATA; + el->regdata.raddr = strtol(value1, NULL, 16); + el->regdata.rdata = strtol(value2, NULL, 16); + } else if (!strcmp(keyword, "PAYLOAD")) { + char *value = strtok_r(NULL, " ", &saveptr); + el->type = IMAGE_CFG_PAYLOAD; + el->payload = strdup(value); + } else { + fprintf(stderr, "Ignoring unknown line '%s'\n", line); + } + + return 0; +} + +static int image_create_config_parse(FILE *fcfg, + struct image_cfg_element *image_cfg) +{ + int ret; + int cfgi = 0; + + /* Parse the configuration file */ + while (!feof(fcfg)) { + char *line; + char buf[256]; + + /* Read the current line */ + memset(buf, 0, sizeof(buf)); + line = fgets(buf, sizeof(buf), fcfg); + if (!line) + break; + + /* Ignore useless lines */ + if (line[0] == '\n' || line[0] == '#') + continue; + + /* Strip final newline */ + if (line[strlen(line) - 1] == '\n') + line[strlen(line) - 1] = 0; + + /* Parse the current line */ + ret = image_create_config_parse_oneline(line, + &image_cfg[cfgi]); + if (ret) + return ret; + + cfgi++; + } + + return 0; +} + +static int image_create_config_count_lines(FILE *fcfg) +{ + int lines = 0; + + /* Count number of useful lines */ + while (!feof(fcfg)) { + char *line; + char buf[256]; + memset(buf, 0, sizeof(buf)); + line = fgets(buf, sizeof(buf), fcfg); + if (!line) + break; + if (line[0] == '\n' || line[0] == '#') + continue; + lines++; + } + + return lines; +} + +static int image_create(const char *input, const char *output) +{ + struct image_cfg_element *image_cfg; + FILE *fcfg, *outputimg; + void *image = NULL; + int version = -1; + size_t imagesz; + int lines; + int cfgi; + int ret; + + fcfg = fopen(input, "r"); + if (!fcfg) { + fprintf(stderr, "Could not open input file %s\n", + input); + return -1; + } + + lines = image_create_config_count_lines(fcfg); + if (lines == 0) { + fprintf(stderr, "Configuration file %s is empty\n", + input); + return -1; + } + + image_cfg = malloc(lines * sizeof(struct image_cfg_element)); + if (!image_cfg) { + fprintf(stderr, "Cannot allocate memory\n"); + fclose(fcfg); + return -1; + } + + memset(image_cfg, 0, lines * sizeof(struct image_cfg_element)); + rewind(fcfg); + + ret = image_create_config_parse(fcfg, image_cfg); + if (ret) + return -1; + + for (cfgi = 0; cfgi < lines; cfgi++) { + if (image_cfg[cfgi].type != IMAGE_CFG_VERSION) + continue; + + version = image_cfg[cfgi].version; + break; + } + + if (version == -1) { + fprintf(stderr, "File %s does not have the VERSION field\n", + input); + return -1; + } + + if (version == 0) + image = image_create_v0(image_cfg, lines, output, &imagesz); + else if (version == 1) + image = image_create_v1(image_cfg, lines, output, &imagesz); + + if (!image) { + fprintf(stderr, "Could not create image\n"); + return -1; + } + + outputimg = fopen(output, "w"); + if (!outputimg) { + fprintf(stderr, "Cannot open output image %s for writing\n", + output); + return -1; + } + + ret = fwrite(image, imagesz, 1, outputimg); + if (ret != 1) { + fprintf(stderr, "Cannot write to output image %s\n", + output); + return -1; + } + + fclose(outputimg); + free(image); + + return 0; +} + +enum { + ACTION_CREATE, + ACTION_EXTRACT, + ACTION_DUMP, + ACTION_HELP, +}; + +int main(int argc, char *argv[]) +{ + int action = -1, opt; + const char *input = NULL, *output = NULL; + + while ((opt = getopt(argc, argv, "hxci:o:")) != -1) { + switch (opt) { + case 'x': + action = ACTION_EXTRACT; + break; + case 'c': + action = ACTION_CREATE; + break; + case 'i': + input = optarg; + break; + case 'o': + output = optarg; + break; + case 'h': + action = ACTION_HELP; + break; + } + } + + /* We should have consumed all arguments */ + if (optind != argc) { + usage(argv[0]); + exit(1); + } + + if (action != ACTION_HELP && !input) { + fprintf(stderr, "Missing input file\n"); + usage(argv[0]); + exit(1); + } + + if ((action == ACTION_EXTRACT || action == ACTION_CREATE) && + !output) { + fprintf(stderr, "Missing output file\n"); + usage(argv[1]); + exit(1); + } + + switch (action) { + case ACTION_EXTRACT: + return image_extract(input, output); + case ACTION_CREATE: + return image_create(input, output); + case ACTION_HELP: + usage(argv[0]); + return 0; + default: + fprintf(stderr, "No action specified\n"); + usage(argv[0]); + exit(1); + } + + return 0; +} -- 1.7.9.5 _______________________________________________ barebox mailing list barebox@xxxxxxxxxxxxxxxxxxx http://lists.infradead.org/mailman/listinfo/barebox