From 26c7f054b5fe8408d64ca7b06372b303cf888245 Mon Sep 17 00:00:00 2001 From: INAGAKI Hiroshi Date: Wed, 8 May 2024 22:36:05 +0900 Subject: [PATCH] nec-usbatermfw: add tool for NEC "USB ATERM" format Add a tool for NEC Aterm devices using "USB ATERM" format. This format is mainly used on the devices based on NetBSD, such as WR8750N, WG600HP and WG1400HP. A workaround in write_blockheader() is needed to resolve an unknown bug when generating an image if built with gcc 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.2) and cmake. example: index 1 and 4 are fixed data and checksum of them shouldn't be changed - with the workaround nec-usbatermfw \ openwrt-ath79-tiny-nec_wr9500n-initramfs-factory.bin.new \ -t H033 \ -f 0x0003 -d loader-nec_wr9500n.bin \ -d openwrt-ath79-tiny-nec_wr9500n-initramfs-factory.bin Flags Total Len. Load Addr Entry Point Checksum 1: 0x0000, 0x0000003e, 0x00000000, 0x00000000, 0x00001988 2: 0x0003, 0x00000a14, 0x80040000, 0x80040000, 0x00001526 3: 0x0002, 0x0054df20, 0x80040000, 0x80040000, 0x0000c41f 4: 0x0001, 0x00000018, 0x00000000, 0x00000000, 0x0000cfff - without the workaround nec-usbatermfw \ openwrt-ath79-tiny-nec_wr9500n-initramfs-factory.bin.new \ -t H033 \ -f 0x0003 -d loader-nec_wr9500n.bin \ -d openwrt-ath79-tiny-nec_wr9500n-initramfs-factory.bin Flags Total Len. Load Addr Entry Point Checksum 1: 0x0000, 0x0000003e, 0x00000000, 0x00000000, 0x00001988 2: 0x0003, 0x00000a14, 0x80040000, 0x80040000, 0x00009528 3: 0x0002, 0x0054df20, 0x80040000, 0x80040000, 0x00004422 4: 0x0001, 0x00000018, 0x00000000, 0x00000000, 0x00005002 Signed-off-by: INAGAKI Hiroshi --- CMakeLists.txt | 1 + src/nec-usbatermfw.c | 305 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 306 insertions(+) create mode 100644 src/nec-usbatermfw.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e356fc..3352e26 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -94,6 +94,7 @@ FW_UTIL(mkzyxelzldfw src/md5.c "" "") FW_UTIL(motorola-bin "" "" "") FW_UTIL(nand_ecc "" "" "") FW_UTIL(nec-enc "" --std=gnu99 "") +FW_UTIL(nec-usbatermfw "" -D_DEFAULT_SOURCE "") FW_UTIL(osbridge-crc "" "" "") FW_UTIL(oseama src/md5.c "" "") FW_UTIL(otrx "" "" "") diff --git a/src/nec-usbatermfw.c b/src/nec-usbatermfw.c new file mode 100644 index 0000000..56438d3 --- /dev/null +++ b/src/nec-usbatermfw.c @@ -0,0 +1,305 @@ +// 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; +} -- 2.30.2