// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2024 INAGAKI Hiroshi */ #include #include #include #include #include #include #include #include #include #define FWHDR_LEN 0x20 #define BLKHDR_LEN 0x18 #define BLKHDR_FLAGS_GZIP 0x80000000 #define BLKHDR_FLAGS_EXEC 0x00000002 #define BLKHDR_FLAGS_SYS 0x00000001 #define BLKHDR_DEF_LOADADDR 0x80040000 #define BLKHDR_DEF_ENTRYP BLKHDR_DEF_LOADADDR #define FILETYPE_LEN 0x20 #define PADBLK_LEN 0x4 #define DATABLK_CNT_MAX 2 struct header { uint32_t flags; uint32_t totlen; uint32_t hdrlen; uint32_t cksum; uint32_t loadaddr; uint32_t entryp; } __attribute__ ((packed)); static char *progname; FILE *outbin; int nblk = 0; static void usage(void) { printf("Usage: %s [OPTIONS...]\n", progname); printf("\n" "Options:\n" " -t set firmware type to \n" " -f set data flags to \n" " -a set data load address to \n" " -e set data entry point to \n" " -d read input data from file (max. %dx)\n", DATABLK_CNT_MAX); } static int strtou32(char *arg, uint32_t *val) { char *endptr = NULL; errno = 0; *val = strtoul(arg, &endptr, 0); return (errno || (endptr && *endptr)) ? -EINVAL : 0; } static int write_data(const char *buf, size_t length, uint32_t *cksum) { size_t padlen = 0; size_t pos; if (length % PADBLK_LEN) padlen = PADBLK_LEN - length % PADBLK_LEN; if (fwrite(buf, 1, length, outbin) != length) { fprintf(stderr, "Couldn't write %zu bytes\n", length); return -EACCES; } if (cksum) for (pos = 0; pos < length; pos += 2) { if (length - pos == 1) *cksum += le16toh(buf[pos]); else *cksum += le16toh(*(uint16_t *)(buf + pos)); } if (padlen) { int i; for (i = 0; i < padlen; i++) fputc(0x0, outbin); } return padlen; } static int write_blockheader(uint32_t flags, uint32_t loadaddr, uint32_t entryp, size_t datalen, int padlen, uint32_t cksum) { struct header hdr; uint16_t *cur = (uint16_t *)&hdr; char buf[0x20]; flags <<= 16; flags |= (~flags & 0xffff0000) >> 16; hdr.flags = htobe32(flags); hdr.totlen = htobe32(BLKHDR_LEN + datalen); hdr.hdrlen = htobe32(BLKHDR_LEN); hdr.cksum = 0; hdr.loadaddr = htobe32(loadaddr); hdr.entryp = htobe32(entryp); for (cur += 2; cur - (uint16_t *)&hdr < BLKHDR_LEN / 2; cur++) { cksum += htole16(*cur); /* * workaround of unknown bug if built with gcc 9.4.0 * (Ubuntu 9.4.0-1ubuntu1~20.04.2) and cmake */ snprintf(buf, sizeof(buf), "%u", cksum); } cksum = 0xffff ^ cksum % 0xffff; hdr.cksum = htole32(cksum); if (datalen) fseek(outbin, -(BLKHDR_LEN + datalen + padlen), SEEK_CUR); if (fwrite(&hdr, 1, BLKHDR_LEN, outbin) != BLKHDR_LEN) { fprintf(stderr, "Couldn't write %d bytes\n", BLKHDR_LEN); return -EACCES; } if (datalen) fseek(outbin, datalen + padlen, SEEK_CUR); nblk++; printf("%d:\t0x%04x,\t0x%08zx,\t0x%08x,\t0x%08x,\t0x%08x\n", nblk, flags >> 16, BLKHDR_LEN + datalen, loadaddr, entryp, cksum); return 0; } static int append_datablock_from_file(uint32_t flags, uint32_t loadaddr, uint32_t entryp, const char *datapath) { FILE *databin; size_t readlen, length = 0; uint32_t cksum = 0; char buf[0x10000]; int ret = 0; fseek(outbin, BLKHDR_LEN, SEEK_CUR); databin = fopen(datapath, "r"); if (!databin) { fprintf(stderr, "couldn't open %s\n", datapath); return -EACCES; } while ((readlen = fread(buf, 1, sizeof(buf), databin)) > 0) { ret = write_data(buf, readlen, &cksum); if (ret < 0) goto exit; length += readlen; } ret = write_blockheader(flags, loadaddr, entryp, length, ret, cksum); exit: fclose(databin); return ret; } static int append_datablock_from_buf(uint32_t flags, uint32_t loadaddr, uint32_t entryp, const char *buf, size_t buflen) { uint32_t cksum = 0; int ret; fseek(outbin, BLKHDR_LEN, SEEK_CUR); ret = write_data(buf, buflen, &cksum); return write_blockheader(flags, loadaddr, entryp, buflen, ret, cksum); } int main(int argc, char **argv) { uint32_t flags = BLKHDR_FLAGS_EXEC; uint32_t loadaddr = BLKHDR_DEF_LOADADDR; uint32_t entryp = BLKHDR_DEF_ENTRYP; char buf[0x40], ftype[FILETYPE_LEN]; int ret, c, ftlen = 0; progname = basename(argv[0]); if (argc >= 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))) { usage(); return 0; } if (argc < 2) { fprintf(stderr, "no output file specified\n"); usage(); return -EINVAL; } outbin = fopen(argv[1], "w+"); if (!outbin) { fprintf(stderr, "Couldn't open %s\n", argv[1]); return -EACCES; } /* add firmware header */ sprintf(buf, "USB ATERMWL3050"); memset(buf + 0x10, 0xff, 0x10); ret = write_data(buf, FWHDR_LEN, NULL); if (ret < 0) goto exit; printf("\tFlags\tTotal Len.\tLoad Addr\tEntry Point\tChecksum\n"); /* add version/copyright block */ ret = sprintf(buf, "VERSION: 9.99.99\nOEM1 VERSION: 9.9.99\n"); ret = append_datablock_from_buf(0x0, 0x0, 0x0, buf, ret); if (ret) goto exit; /* set type and parse/write user-defined data blocks */ while((c = getopt(argc, argv, "f:a:e:d:t:")) != -1) { switch (c) { case 'f': if (strtou32(optarg, &flags)) { fprintf(stderr, "invalid flags value specified\n"); ret = -EINVAL; goto exit; } break; case 'a': if (strtou32(optarg, &loadaddr)) { fprintf(stderr, "invalid load address specified\n"); ret = -EINVAL; goto exit; } break; case 'e': if (strtou32(optarg, &entryp)) { fprintf(stderr, "invalid entry point specified\n"); ret = -EINVAL; goto exit; } break; case 'd': if (nblk - 1 >= DATABLK_CNT_MAX) { fprintf(stderr, "data block count exceeds maximum count (%d), skipping...\n", DATABLK_CNT_MAX); continue; } ret = append_datablock_from_file(flags, loadaddr, entryp, optarg); if (ret) goto exit; flags = BLKHDR_FLAGS_EXEC; loadaddr = BLKHDR_DEF_LOADADDR; entryp = BLKHDR_DEF_ENTRYP; break; case 't': ftlen = snprintf(buf, sizeof(buf), "Binary Type%s File END \r\n", optarg); if (ftlen > FILETYPE_LEN) { fprintf(stderr, "specified type is too long\n"); ret = -EINVAL; goto exit; } memset(ftype, 0xff, FILETYPE_LEN); strncpy(ftype + (FILETYPE_LEN - ftlen), buf, ftlen); break; case '?': default: ret = -EINVAL; goto exit; } } if (!ftlen) { fprintf(stderr, "no file type specified\n"); ret = -EINVAL; goto exit; } /* append end block */ ret = write_blockheader(BLKHDR_FLAGS_SYS, 0, 0, 0, 0, 0); if (ret) goto exit; /* append file type */ ret = write_data(ftype, FILETYPE_LEN, NULL); exit: fclose(outbin); if (ret == -EINVAL) usage(); return ret < 0 ? ret : 0; }