nec-usbatermfw: add tool for NEC "USB ATERM" format
authorINAGAKI Hiroshi <musashino.open@gmail.com>
Wed, 8 May 2024 13:36:05 +0000 (22:36 +0900)
committerINAGAKI Hiroshi <musashino.open@gmail.com>
Sun, 4 Aug 2024 13:39:57 +0000 (22:39 +0900)
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 <musashino.open@gmail.com>
CMakeLists.txt
src/nec-usbatermfw.c [new file with mode: 0644]

index 6e356fc3b7aa6bd1b61863845001fb7905f1ac39..3352e268c591f79d11c432d3273aeef6048edc68 100644 (file)
@@ -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 (file)
index 0000000..56438d3
--- /dev/null
@@ -0,0 +1,305 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2024 INAGAKI Hiroshi <musashino.open@gmail.com>
+ */
+
+#include <byteswap.h>
+#include <endian.h>
+#include <errno.h>
+#include <libgen.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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 <output> [OPTIONS...]\n", progname);
+       printf("\n"
+              "Options:\n"
+              "  -t <type>       set firmware type to <type>\n"
+              "  -f <flags>      set data flags to <flags>\n"
+              "  -a <loadaddr>   set data load address to <loadaddr>\n"
+              "  -e <entry>      set data entry point to <entry>\n"
+              "  -d <file>       read input data from file <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;
+}