// SPDX-License-Identifier: GPL-2.0 #include #include #include #include #include #include #include #include #include #define ALIGN_SUP(x, a) (((x) + ((a) - 1)) & ~((a) - 1)) #define min(a, b) ((a) < (b) ? (a) : (b)) #define is_power_of_two(a) (((a) & ((a) - 1)) == 0) #define container_of(ptr, type, member) \ (type *)((char *)(ptr) - (char *) &((type *)0)->member) #define pr_err(fmt, arg...) fprintf(stderr, "ERROR: " fmt, ##arg) #define pr_info(fmt, arg...) fprintf(stderr, "INFO: " fmt, ##arg) static const struct { unsigned char id; char *name; } boot_ids[] = { { 0x4d, "i2c" }, { 0x5a, "spi" }, { 0x69, "uart" }, { 0x78, "sata" }, { 0x8b, "nand" }, { 0x9c, "pex" }, { /* sentinel */ } }; struct mvebuimg_checksum_method { void (*update)(struct mvebuimg_checksum_method *self, const void *buf, size_t len); }; struct mvebuimg_checksum_method8 { struct mvebuimg_checksum_method method; uint8_t csum; }; static void mvebuimg_checksum8(struct mvebuimg_checksum_method *self, const void *buf, size_t len) { struct mvebuimg_checksum_method8 *realself = container_of(self, struct mvebuimg_checksum_method8, method); while (len--) { realself->csum += *(uint8_t *)buf; buf++; } } struct mvebuimg_checksum_method32 { struct mvebuimg_checksum_method method; uint32_t csum; int mod; }; static void mvebuimg_checksum32(struct mvebuimg_checksum_method *self, const void *buf, size_t len) { struct mvebuimg_checksum_method32 *realself = container_of(self, struct mvebuimg_checksum_method32, method); while (len--) { realself->csum += *(uint8_t *)buf << (8 * realself->mod); realself->mod = (realself->mod + 1) % 4; buf++; } } static ssize_t mvebuimg_sendfile(int outfd, int infd, struct mvebuimg_checksum_method *csumm, size_t count) { char buf[1024]; size_t head = 0, tail = 0; int ret; size_t count_read = 0, count_written = 0; while (count_written < count) { if (head == tail) { head = 0; tail = 0; } if (tail < sizeof(buf) && count_read < count) { ret = read(infd, buf + tail, min(count, sizeof(buf) - tail)); if (ret < 0) { pr_err("read error\n"); return ret; } if (ret == 0) { pr_err("hit EOF\n"); return -EINVAL; } if (csumm) csumm->update(csumm, buf + tail, ret); tail += ret; count_read += ret; } if (head < tail) { ret = write(outfd, buf + head, tail - head); if (ret < 0) return ret; head += ret; count_written += ret; } } return 0; } static int mvebuimg_v1_write_binhdr(int fd, size_t *offset, const char *filename, struct mvebuimg_checksum_method8 *summ, int is_last_header) { int binhdr_fd; char binhdr[16] = { 0, }; struct stat s; uint32_t hdrsize; int ret; off_t ret_lseek; char trailer[4] = { 0, }; binhdr[0] = 0x2; binhdr_fd = open(filename, O_RDONLY); if (binhdr_fd < 0) { pr_err("Failed to open binhdr source \"%s\" (%s)\n", filename, strerror(errno)); return -errno; } ret = fstat(binhdr_fd, &s); if (ret < 0) { pr_err("Failed to stat binhdr source \"%s\" (%s)\n", filename, strerror(errno)); return -errno; } if (ALIGN_SUP(s.st_size, 4) > 0xffffff - 0x14) { pr_err("binhdr \"%s\" too big\n", filename); return -EINVAL; } hdrsize = /* len of hdr's header */ 0x10 + /* actual image */ ALIGN_SUP(s.st_size, 4) + /* trailer word */ 4; binhdr[1] = hdrsize >> 16; binhdr[2] = hdrsize; binhdr[3] = hdrsize >> 8; /* parameters that are used by all images I know of */ binhdr[4] = 2; binhdr[8] = 0x5b; binhdr[12] = 0x68; summ->method.update(&summ->method, binhdr, sizeof(binhdr)); ret = pwrite(fd, binhdr, sizeof(binhdr), *offset); if (ret < 0) { pr_err("failed to write header of binhdr \"%s\"\n", filename); return -errno; } *offset += sizeof(binhdr); ret_lseek = lseek(fd, *offset, SEEK_SET); if (ret_lseek == (off_t)-1) { pr_err("failed to seek to binhdr offset\n"); return -errno; } ret = mvebuimg_sendfile(fd, binhdr_fd, &summ->method, s.st_size); if (ret < 0) return ret; *offset += ALIGN_SUP(s.st_size, 4); trailer[0] = is_last_header ? 0 : 1; summ->method.update(&summ->method, trailer, sizeof(4)); ret = pwrite(fd, trailer, sizeof(trailer), *offset); if (ret < 0) { pr_err("failed to write trailer of binhdr \"%s\"\n", filename); return -errno; } *offset += 4; return 0; } static int mvebuimg_v1_write_payload(int fd, size_t *offset, const char *filename) { int payload_fd; struct stat s; int ret; char pad[4] = { 0, }; struct mvebuimg_checksum_method32 csumm32 = { .method = { .update = mvebuimg_checksum32, }, .csum = 0, }; payload_fd = open(filename, O_RDONLY); if (payload_fd < 0) { pr_err("Failed to open payload \"%s\" (%s)\n", filename, strerror(errno)); return -errno; } ret = fstat(payload_fd, &s); if (ret < 0) { pr_err("Failed to stat payload \"%s\" (%s)\n", filename, strerror(errno)); return -errno; } lseek(fd, *offset, SEEK_SET); ret = mvebuimg_sendfile(fd, payload_fd, &csumm32.method, s.st_size); if (ret < 0) return ret; /* pad to a multiple of 4 bytes */ ret = pwrite(fd, pad, ALIGN_SUP(s.st_size, 4) - s.st_size, *offset + s.st_size); if (ret < 0) { pr_err("failed to write padding of payload\n"); return -errno; } *offset += ALIGN_SUP(s.st_size, 4); /* For version 1 images the checksum doesn't seem to be necessary. We're * still adding it for now to create bytewise identical images as * kwbimage. */ pad[0] = csumm32.csum; pad[1] = csumm32.csum >> 8; pad[2] = csumm32.csum >> 16; pad[3] = csumm32.csum >> 24; ret = pwrite(fd, pad, 4, *offset); *offset += 4; return 0; } /* * Usage: create [-b binhdr]* [-o outputfilename] bootsrc payload */ static int mvebuimg_v1_create(int argc, char *const argv[]) { int opt; char *output = NULL; unsigned int extensionheaders = 0; int fd, ret; long l; char *endptr; char mainhdr[0x20] = { 0, }; struct mvebuimg_checksum_method8 csumm = { .method = { .update = mvebuimg_checksum8, }, .csum = 0, }; size_t offset, offset_payload; size_t size_payload; unsigned long payload_align = 4096; int debug = 0; long destaddr, execaddr; int destaddr_provided = 0, execaddr_provided = 0; uint8_t nand_blksz = 0; uint8_t nand_bbloc = 0; optind = 0; while ((opt = getopt(argc, argv, "+a:b:B:Dd:e:o:")) != -1) { switch (opt) { case 'a': errno = 0; payload_align = strtol(optarg, &endptr, 0); if (errno || *endptr != '\0' || !is_power_of_two(payload_align)) { pr_err("Invalid payload alignment, must be a power of two\n"); return EXIT_FAILURE; } break; case 'b': ++extensionheaders; break; case 'B': errno = 0; l = strtol(optarg, &endptr, 0); if (errno || (*endptr != '\0' && *endptr != ':') || (l != 0 && (l < 0x10000 || l > 0xff0000 || !is_power_of_two(l)))) { pr_err("Invalid NAND block size\n"); return EXIT_FAILURE; } nand_blksz = l >> 16; if (*endptr != ':') break; if ((*(endptr + 1) == '0' || *(endptr + 1) == '1') && *(endptr + 2) == '\0') { nand_bbloc = *(endptr + 1) - '0'; } else { pr_err("Invalid NAND bad block location\n"); return EXIT_FAILURE; } break; case 'D': debug = 1; break; case 'd': errno = 0; destaddr = strtol(optarg, &endptr, 0); if (errno || *endptr != '\0' || destaddr < 0 || destaddr > 0xffffffff) { pr_err("invalid dest address\n"); return EXIT_FAILURE; } destaddr_provided = 1; break; case 'e': errno = 0; execaddr = strtol(optarg, &endptr, 0); if (errno || *endptr != '\0' || execaddr < 0 || execaddr > 0xffffffff) { pr_err("invalid exec address\n"); return EXIT_FAILURE; } execaddr_provided = 1; break; case 'o': if (output) { pr_err("more than one output file specified\n"); return EXIT_FAILURE; } output = optarg; break; default: pr_err("Unsupported option\n"); return EXIT_FAILURE; } } if (argc - optind < 2) { pr_err("too few parameters\n"); return EXIT_FAILURE; } errno = 0; l = strtol(argv[optind], &endptr, 0); if (errno || *endptr != '\0' || l < 0 || l > 0xff) { int i; int idfound = 0; for (i = 0; boot_ids[i].name; ++i) { if (strcmp(boot_ids[i].name, argv[optind]) == 0) { mainhdr[0] = boot_ids[i].id; idfound = 1; break; } } if (!idfound) { fprintf(stderr, "failed to parse bootsrc\n"); return EXIT_FAILURE; } } else { mainhdr[0] = l; } if (!destaddr_provided) destaddr = 0x1000000; if (!execaddr_provided) execaddr = destaddr; if (!output) { pr_err("no output option given\n"); return EXIT_FAILURE; } fd = open(output, O_CREAT | O_WRONLY | O_TRUNC, 0644); if (fd < 0) { pr_err("failed to open output\n"); return EXIT_FAILURE; } if (extensionheaders) mainhdr[0x1e] = 1; offset = 0x20; optind = 0; while ((opt = getopt(argc, argv, "+a:b:B:Dd:e:o:")) != -1) { switch (opt) { case 'b': --extensionheaders; ret = mvebuimg_v1_write_binhdr(fd, &offset, optarg, &csumm, extensionheaders == 0); if (ret < 0) return EXIT_FAILURE; break; default: break; } } /* Align the payload to a reasonable boundary */ offset = offset_payload = ALIGN_SUP(offset, 4096); /* BootROM debugging */ mainhdr[0x01] = debug ? 0x1 : 0x0; /* Boot Image Format Version */ mainhdr[0x08] = 0x1; /* Header size */ mainhdr[0x09] = offset_payload >> 16; mainhdr[0x0a] = offset_payload; mainhdr[0x0b] = offset_payload >> 8; /* Offset of payload in the image */ mainhdr[0x0c] = (char)offset_payload; mainhdr[0x0d] = (char)(offset_payload >> 8); mainhdr[0x0e] = (char)(offset_payload >> 16); mainhdr[0x0f] = (char)(offset_payload >> 24); /* Address to load the image to */ mainhdr[0x10] = (char)destaddr; mainhdr[0x11] = (char)(destaddr >> 8); mainhdr[0x12] = (char)(destaddr >> 16); mainhdr[0x13] = (char)(destaddr >> 24); /* Entry point */ mainhdr[0x14] = (char)execaddr; mainhdr[0x15] = (char)(execaddr >> 8); mainhdr[0x16] = (char)(execaddr >> 16); mainhdr[0x17] = (char)(execaddr >> 24); /* For NAND images set block size and bad block location */ mainhdr[0x19] = nand_blksz; mainhdr[0x1a] = nand_bbloc; ret = mvebuimg_v1_write_payload(fd, &offset, argv[optind + 1]); if (ret < 0) return EXIT_FAILURE; size_payload = offset - offset_payload; mainhdr[0x04] = (char)size_payload; mainhdr[0x05] = (char)(size_payload >> 8); mainhdr[0x06] = (char)(size_payload >> 16); mainhdr[0x07] = (char)(size_payload >> 24); csumm.method.update(&csumm.method, mainhdr, sizeof(mainhdr)); mainhdr[0x1f] = csumm.csum; ret = pwrite(fd, mainhdr, sizeof(mainhdr), 0); if (ret < 0) { pr_err("failure write main header (%s)\n", strerror(errno)); return EXIT_FAILURE; } ret = close(fd); if (ret) { pr_err("failure during output closing (%s)\n", strerror(errno)); return EXIT_FAILURE; } return EXIT_SUCCESS; } static const struct { const char *name; int (*func[2])(int argc, char *const argv[]); } commands[] = { { .name = "create", .func = { NULL, mvebuimg_v1_create } }, { /* sentinel */ } }; int main(int argc, char *const argv[]) { int opt; long version = -1; int i; char *endptr; optind = 0; while ((opt = getopt(argc, argv, "+v:")) != -1) { switch (opt) { case 'v': errno = 0; version = strtol(optarg, &endptr, 0); if (errno || *endptr != '\0' || version < 0 || version > 1) { pr_err("Unsupported version\n"); return EXIT_FAILURE; } break; default: pr_err("Unsupported option\n"); return EXIT_FAILURE; } } if (version < 0) { pr_info("Defaulting to version 1\n"); version = 1; } argc -= optind; argv += optind; if (argc < 1) { pr_err("No command given\n"); return EXIT_FAILURE; } for (i = 0; commands[i].name != NULL; ++i) { if (strcmp(commands[i].name, argv[0]) == 0) { if (!commands[i].func[version]) { pr_err("ERROR: command not implemented\n"); return EXIT_FAILURE; } return commands[i].func[version](argc, argv); } } pr_err("command \"%s\" not found\n", argv[0]); return EXIT_FAILURE; }