Update hid_bootloader_cli code
authorDrashna Jael're <drashna@live.com>
Thu, 15 Jul 2021 15:06:14 +0000 (08:06 -0700)
committerDrashna Jael're <drashna@live.com>
Thu, 15 Jul 2021 15:06:14 +0000 (08:06 -0700)
Bootloaders/HID/HostLoaderApp/Makefile
Bootloaders/HID/HostLoaderApp/hid_bootloader_cli.c

index d7d6458..3fbc7bd 100644 (file)
@@ -3,31 +3,50 @@ OS ?= LINUX
 #OS ?= MACOSX
 #OS ?= BSD
 
+# uncomment this to use libusb on Macintosh, instead of Apple's HID manager via IOKit
+# this is technically not the "correct" way to support Macs, but it's been reported to
+# work.
+#USE_LIBUSB ?= YES
+
 ifeq ($(OS), LINUX)  # also works on FreeBSD
 CC ?= gcc
 CFLAGS ?= -O2 -Wall
 hid_bootloader_cli: hid_bootloader_cli.c
-       $(CC) $(CFLAGS) -s -DUSE_LIBUSB -o hid_bootloader_cli hid_bootloader_cli.c -lusb
+       $(CC) $(CFLAGS) $(CPPFLAGS) -DUSE_LIBUSB -o hid_bootloader_cli hid_bootloader_cli.c -lusb $(LDFLAGS)
 
 
 else ifeq ($(OS), WINDOWS)
-CC = i586-mingw32msvc-gcc
+CC ?= i586-mingw32msvc-gcc
 CFLAGS ?= -O2 -Wall
-LDLIB = -lsetupapi -lhid
 hid_bootloader_cli.exe: hid_bootloader_cli.c
-       $(CC) $(CFLAGS) -s -DUSE_WIN32 -o hid_bootloader_cli.exe hid_bootloader_cli.c $(LDLIB)
+       $(CC) $(CFLAGS) -s -DUSE_WIN32 -o hid_bootloader_cli.exe hid_bootloader_cli.c -lhid -lsetupapi -lwinmm
 
 
 else ifeq ($(OS), MACOSX)
+ifeq ($(USE_LIBUSB), YES)
+CC ?= gcc
+CFLAGS ?= -O2 -Wall
+hid_bootloader_cli: hid_bootloader_cli.c
+       $(CC) $(CFLAGS) -DUSE_LIBUSB -DMACOSX -o hid_bootloader_cli hid_bootloader_cli.c -lusb -I /usr/local/include -L/usr/local/lib
+        
+else
 CC ?= gcc
-SDK ?= /Developer/SDKs/MacOSX10.5.sdk
+SDK ?= $(shell xcrun --show-sdk-path)
+#SDK ?= /Developer/SDKs/MacOSX10.6.sdk  # the old way...
+#SDK = /Developer_xcode32/SDKs/MacOSX10.5.sdk  # the very old way!
+#CC = /Developer_xcode32/usr/bin/gcc-4.0
+#CFLAGS = -O2 -Wall -arch i386 -arch ppc
 CFLAGS ?= -O2 -Wall
 hid_bootloader_cli: hid_bootloader_cli.c
+ifeq ($(SDK),)
+       $(error SDK was not found. To use this type of compilation please install Xcode)
+endif
        $(CC) $(CFLAGS) -DUSE_APPLE_IOKIT -isysroot $(SDK) -o hid_bootloader_cli hid_bootloader_cli.c -Wl,-syslibroot,$(SDK) -framework IOKit -framework CoreFoundation
 
+endif
 
 else ifeq ($(OS), BSD)  # works on NetBSD and OpenBSD
-CC ?= gcct
+CC ?= gcc
 CFLAGS ?= -O2 -Wall
 hid_bootloader_cli: hid_bootloader_cli.c
        $(CC) $(CFLAGS) -s -DUSE_UHID -o hid_bootloader_cli hid_bootloader_cli.c
@@ -37,4 +56,4 @@ endif
 
 
 clean:
-       rm -f hid_bootloader_cli hid_bootloader_cli.exe
+       rm -f hid_bootloader_cli hid_bootloader_cli.exe*
index b54f943..f29dc81 100644 (file)
@@ -7,8 +7,7 @@
 /* Teensy Loader, Command Line Interface
  * Program and Reboot Teensy Board with HalfKay Bootloader
  * http://www.pjrc.com/teensy/loader_cli.html
- * Copyright 2008-2010, PJRC.COM, LLC
- *
+ * Copyright 2008-2016, PJRC.COM, LLC
  *
  * You may redistribute this program and/or modify it under the terms
  * of the GNU General Public License as published by the Free Software
@@ -32,6 +31,7 @@
  * http://www.pjrc.com/teensy/49-teensy.rules
  */
 
+
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdint.h>
 #include <string.h>
 #include <unistd.h>
 
-void usage(void)
+void usage(const char *err)
 {
-       fprintf(stderr, "Usage: hid_bootloader_cli -mmcu=<MCU> [-w] [-h] [-n] [-v] <file.hex>\n");
-       fprintf(stderr, "\t-w : Wait for device to appear\n");
-       fprintf(stderr, "\t-r : Use hard reboot if device not online\n");
-       fprintf(stderr, "\t-n : No reboot after programming\n");
-       fprintf(stderr, "\t-v : Verbose output\n");
-       fprintf(stderr, "\n<MCU> = atmegaXXuY or at90usbXXXY");
-
-       fprintf(stderr, "\nFor support and more information, please visit:\n");
-       fprintf(stderr, "http://www.lufa-lib.org\n");
-
-       fprintf(stderr, "\nBased on the TeensyHID command line programmer software:\n");
-       fprintf(stderr, "http://www.pjrc.com/teensy/loader_cli.html\n");
+       if(err != NULL) fprintf(stderr, "%s\n\n", err);
+       fprintf(stderr,
+               "Usage: hid_bootloader_cli --mcu=<MCU> [-w] [-h] [-n] [-b] [-v] <file.hex>\n"
+               "\t-w : Wait for device to appear\n"
+               "\t-r : Use hard reboot if device not online\n"
+               "\t-s : Use soft reboot if device not online (Teensy 3.x & 4.x)\n"
+               "\t-n : No reboot after programming\n"
+               "\t-b : Boot only, do not program\n"
+               "\t-v : Verbose output\n"
+               "\nUse `hid_bootloader_cli --list-mcus` to list supported MCUs.\n"
+               "\nFor support and more information, please visit:\n"
+               "\thttp://www.lufa-lib.org\n"
+
+               "\nBased on the TeensyHID command line programmer software:\n"
+               "\thttp://www.pjrc.com/teensy/loader_cli.html\n");
        exit(1);
 }
 
@@ -61,23 +64,28 @@ int teensy_open(void);
 int teensy_write(void *buf, int len, double timeout);
 void teensy_close(void);
 int hard_reboot(void);
+int soft_reboot(void);
 
 // Intel Hex File Functions
 int read_intel_hex(const char *filename);
 int ihex_bytes_within_range(int begin, int end);
 void ihex_get_data(int addr, int len, unsigned char *bytes);
+int memory_is_blank(int addr, int block_size);
 
 // Misc stuff
 int printf_verbose(const char *format, ...);
 void delay(double seconds);
 void die(const char *str, ...);
 void parse_options(int argc, char **argv);
+void boot(unsigned char *buf, int write_size);
 
 // options (from user via command line args)
 int wait_for_device_to_appear = 0;
 int hard_reboot_device = 0;
+int soft_reboot_device = 0;
 int reboot_after_programming = 1;
 int verbose = 0;
+int boot_only = 0;
 int code_size = 0, block_size = 0;
 const char *filename=NULL;
 
@@ -90,27 +98,35 @@ const char *filename=NULL;
 
 int main(int argc, char **argv)
 {
-       unsigned char buf[260];
-       int num, addr, r, first_block=1, waited=0;
+       unsigned char buf[2048];
+       int num, addr, r, write_size;
+
+       int first_block=1, waited=0;
 
        // parse command line arguments
        parse_options(argc, argv);
-       if (!filename) {
-               fprintf(stderr, "Filename must be specified\n\n");
-               usage();
+       if (!filename && !boot_only) {
+               usage("Filename must be specified");
        }
        if (!code_size) {
-               fprintf(stderr, "MCU type must be specified\n\n");
-               usage();
+               usage("MCU type must be specified");
        }
-       printf_verbose("Teensy Loader, Command Line, Version 2.0\n");
+       printf_verbose("Teensy Loader, Command Line, Version 2.2\n");
 
-       // read the intel hex file
-       // this is done first so any error is reported before using USB
-       num = read_intel_hex(filename);
-       if (num < 0) die("error reading intel hex file \"%s\"", filename);
-       printf_verbose("Read \"%s\": %d bytes, %.1f%% usage\n",
-               filename, num, (double)num / (double)code_size * 100.0);
+       if (block_size == 512 || block_size == 1024) {
+               write_size = block_size + 64;
+       } else {
+               write_size = block_size + 2;
+       };
+
+       if (!boot_only) {
+               // read the intel hex file
+               // this is done first so any error is reported before using USB
+               num = read_intel_hex(filename);
+               if (num < 0) die("error reading intel hex file \"%s\"", filename);
+               printf_verbose("Read \"%s\": %d bytes, %.1f%% usage\n",
+                       filename, num, (double)num / (double)code_size * 100.0);
+       }
 
        // open the USB device
        while (1) {
@@ -121,7 +137,14 @@ int main(int argc, char **argv)
                        hard_reboot_device = 0; // only hard reboot once
                        wait_for_device_to_appear = 1;
                }
-               if (!wait_for_device_to_appear) die("Unable to open device\n");
+               if (soft_reboot_device) {
+                       if (soft_reboot()) {
+                               printf_verbose("Soft reboot performed\n");
+                       }
+                       soft_reboot_device = 0;
+                       wait_for_device_to_appear = 1;
+               }
+               if (!wait_for_device_to_appear) die("Unable to open device (hint: try -w option)\n");
                if (!waited) {
                        printf_verbose("Waiting for Teensy device...\n");
                        printf_verbose(" (hint: press the reset button)\n");
@@ -131,6 +154,12 @@ int main(int argc, char **argv)
        }
        printf_verbose("Found HalfKay Bootloader\n");
 
+       if (boot_only) {
+               boot(buf, write_size);
+               teensy_close();
+               return 0;
+       }
+
        // if we waited for the device, read the hex file again
        // perhaps it changed while we were waiting?
        if (waited) {
@@ -144,21 +173,34 @@ int main(int argc, char **argv)
        printf_verbose("Programming");
        fflush(stdout);
        for (addr = 0; addr < code_size; addr += block_size) {
-               if (addr > 0 && !ihex_bytes_within_range(addr, addr + block_size - 1)) {
+               if (!first_block && !ihex_bytes_within_range(addr, addr + block_size - 1)) {
                        // don't waste time on blocks that are unused,
                        // but always do the first one to erase the chip
                        continue;
                }
+               if (!first_block && memory_is_blank(addr, block_size)) continue;
                printf_verbose(".");
-               if (code_size < 0x10000) {
+               if (block_size <= 256 && code_size < 0x10000) {
                        buf[0] = addr & 255;
                        buf[1] = (addr >> 8) & 255;
-               } else {
+                       ihex_get_data(addr, block_size, buf + 2);
+                       write_size = block_size + 2;
+               } else if (block_size == 256) {
                        buf[0] = (addr >> 8) & 255;
                        buf[1] = (addr >> 16) & 255;
+                       ihex_get_data(addr, block_size, buf + 2);
+                       write_size = block_size + 2;
+               } else if (block_size == 512 || block_size == 1024) {
+                       buf[0] = addr & 255;
+                       buf[1] = (addr >> 8) & 255;
+                       buf[2] = (addr >> 16) & 255;
+                       memset(buf + 3, 0, 61);
+                       ihex_get_data(addr, block_size, buf + 64);
+                       write_size = block_size + 64;
+               } else {
+                       die("Unknown code/block size\n");
                }
-               ihex_get_data(addr, block_size, buf + 2);
-               r = teensy_write(buf, block_size + 2, first_block ? 3.0 : 0.25);
+               r = teensy_write(buf, write_size, first_block ? 5.0 : 0.5);
                if (!r) die("error writing to Teensy\n");
                first_block = 0;
        }
@@ -166,11 +208,7 @@ int main(int argc, char **argv)
 
        // reboot to the user's new code
        if (reboot_after_programming) {
-               printf_verbose("Booting\n");
-               buf[0] = 0xFF;
-               buf[1] = 0xFF;
-               memset(buf + 2, 0, sizeof(buf) - 2);
-               teensy_write(buf, block_size + 2, 0.25);
+               boot(buf, write_size);
        }
        teensy_close();
        return 0;
@@ -183,6 +221,14 @@ int main(int argc, char **argv)
 /*                                                              */
 /*             USB Access - libusb (Linux & FreeBSD)            */
 /*                                                              */
+/*  Uses libusb v0.1. To install:                               */
+/*  - [debian, ubuntu, mint] apt install libusb-dev             */
+/*  - [redhat, centos]       yum install libusb-devel           */
+/*  - [fedora]               dnf install libusb-devel           */
+/*  - [arch linux]           pacman -S libusb-compat            */
+/*  - [gentoo]               emerge dev-libs/libusb-compat      */
+/*                                                              */
+/*  - [freebsd]              seems to be preinstalled           */
 /****************************************************************/
 
 #if defined(USE_LIBUSB)
@@ -195,9 +241,7 @@ usb_dev_handle * open_usb_device(int vid, int pid)
        struct usb_bus *bus;
        struct usb_device *dev;
        usb_dev_handle *h;
-       #ifdef LIBUSB_HAS_GET_DRIVER_NP
        char buf[128];
-       #endif
        int r;
 
        usb_init();
@@ -215,7 +259,7 @@ usb_dev_handle * open_usb_device(int vid, int pid)
                        if (dev->descriptor.idProduct != pid) continue;
                        h = usb_open(dev);
                        if (!h) {
-                               printf_verbose("Found device but unable to open");
+                               printf_verbose("Found device but unable to open\n");
                                continue;
                        }
                        #ifdef LIBUSB_HAS_GET_DRIVER_NP
@@ -224,7 +268,7 @@ usb_dev_handle * open_usb_device(int vid, int pid)
                                r = usb_detach_kernel_driver_np(h, 0);
                                if (r < 0) {
                                        usb_close(h);
-                                       printf_verbose("Device is in use by \"%s\" driver", buf);
+                                       printf_verbose("Device is in use by \"%s\" driver\n", buf);
                                        continue;
                                }
                        }
@@ -232,12 +276,15 @@ usb_dev_handle * open_usb_device(int vid, int pid)
                        // Mac OS-X - removing this call to usb_claim_interface() might allow
                        // this to work, even though it is a clear misuse of the libusb API.
                        // normally Apple's IOKit should be used on Mac OS-X
+                       #if !defined(MACOSX)
                        r = usb_claim_interface(h, 0);
                        if (r < 0) {
                                usb_close(h);
-                               printf_verbose("Unable to claim interface, check USB permissions");
+                               printf_verbose("Unable to claim interface, check USB permissions\n");
                                continue;
                        }
+                       #endif
+
                        return h;
                }
        }
@@ -254,8 +301,8 @@ int teensy_open(void)
        if (!libusb_teensy_handle)
                libusb_teensy_handle = open_usb_device(0x03eb, 0x2067);
 
-       if (!libusb_teensy_handle) return 0;
-       return 1;
+       if (libusb_teensy_handle) return 1;
+       return 0;
 }
 
 int teensy_write(void *buf, int len, double timeout)
@@ -263,10 +310,15 @@ int teensy_write(void *buf, int len, double timeout)
        int r;
 
        if (!libusb_teensy_handle) return 0;
-       r = usb_control_msg(libusb_teensy_handle, 0x21, 9, 0x0200, 0, (char *)buf,
-               len, (int)(timeout * 1000.0));
-       if (r < 0) return 0;
-       return 1;
+       while (timeout > 0) {
+               r = usb_control_msg(libusb_teensy_handle, 0x21, 9, 0x0200, 0,
+                       (char *)buf, len, (int)(timeout * 1000.0));
+               if (r >= 0) return 1;
+               //printf("teensy_write, r=%d\n", r);
+               usleep(10000);
+               timeout -= 0.01;  // TODO: subtract actual elapsed time
+       }
+       return 0;
 }
 
 void teensy_close(void)
@@ -295,6 +347,36 @@ int hard_reboot(void)
        return 1;
 }
 
+int soft_reboot(void)
+{
+       usb_dev_handle *serial_handle = NULL;
+
+       serial_handle = open_usb_device(0x16C0, 0x0483);
+
+       if (!serial_handle) 
+               serial_handle = open_usb_device(0x03eb, 0x2067);
+
+       if (!serial_handle) {
+               char *error = usb_strerror();
+               printf("Error opening USB device: %s\n", error);
+               return 0;
+       }
+
+       char reboot_command[] = {0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08};
+       int response = usb_control_msg(serial_handle, 0x21, 0x20, 0, 0, reboot_command, sizeof reboot_command, 10000);
+
+       usb_release_interface(serial_handle, 0);
+       usb_close(serial_handle);
+
+       if (response < 0) {
+               char *error = usb_strerror();
+               printf("Unable to soft reboot with USB error: %s\n", error);
+               return 0;
+       }
+
+       return 1;
+}
+
 #endif
 
 
@@ -368,7 +450,7 @@ HANDLE open_usb_device(int vid, int pid)
 int write_usb_device(HANDLE h, void *buf, int len, int timeout)
 {
        static HANDLE event = NULL;
-       unsigned char tmpbuf[1040];
+       unsigned char tmpbuf[1089];
        OVERLAPPED ov;
        DWORD n, r;
 
@@ -392,9 +474,21 @@ int write_usb_device(HANDLE h, void *buf, int len, int timeout)
                if (r != WAIT_OBJECT_0) return 0;
        }
        if (!GetOverlappedResult(h, &ov, &n, FALSE)) return 0;
+       if (n <= 0) return 0;
        return 1;
 }
 
+void print_win32_err(void)
+{
+       char buf[256];
+       DWORD err;
+
+       err = GetLastError();
+       FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err,
+               0, buf, sizeof(buf), NULL);
+       printf("err %ld: %s\n", err, buf);
+}
+
 static HANDLE win32_teensy_handle = NULL;
 
 int teensy_open(void)
@@ -405,16 +499,27 @@ int teensy_open(void)
        if (!win32_teensy_handle)
                win32_teensy_handle = open_usb_device(0x03eb, 0x2067);
 
-       if (!win32_teensy_handle) return 0;
-       return 1;
+
+       if (win32_teensy_handle) return 1;
+       return 0;
 }
 
 int teensy_write(void *buf, int len, double timeout)
 {
        int r;
+       uint32_t begin, now, total;
+
        if (!win32_teensy_handle) return 0;
-       r = write_usb_device(win32_teensy_handle, buf, len, (int)(timeout * 1000.0));
-       return r;
+       total = (uint32_t)(timeout * 1000.0);
+       begin = timeGetTime();
+       now = begin;
+       do {
+               r = write_usb_device(win32_teensy_handle, buf, len, total - (now - begin));
+               if (r > 0) return 1;
+               Sleep(10);
+               now = timeGetTime();
+       } while (now - begin < total);
+       return 0;
 }
 
 void teensy_close(void)
@@ -440,6 +545,12 @@ int hard_reboot(void)
        return r;
 }
 
+int soft_reboot(void)
+{
+       printf("Soft reboot is not implemented for Win32\n");
+       return 0;
+}
+
 #endif
 
 
@@ -542,7 +653,7 @@ void init_hid_manager(void)
                IOHIDManagerUnscheduleFromRunLoop(hid_manager,
                        CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
                CFRelease(hid_manager);
-               printf_verbose("Error opening HID Manager");
+               printf_verbose("Error opening HID Manager\n");
        }
 }
 
@@ -590,8 +701,8 @@ int teensy_open(void)
        if (!iokit_teensy_reference)
                iokit_teensy_reference = open_usb_device(0x03eb, 0x2067);
 
-       if (!iokit_teensy_reference) return 0;
-       return 1;
+       if (iokit_teensy_reference) return 1;
+       return 0;
 }
 
 int teensy_write(void *buf, int len, double timeout)
@@ -603,9 +714,15 @@ int teensy_write(void *buf, int len, double timeout)
        // even though Apple documents it with a code example!
        // submitted to Apple on 22-sep-2009, problem ID 7245050
        if (!iokit_teensy_reference) return 0;
-       ret = IOHIDDeviceSetReport(iokit_teensy_reference,
-               kIOHIDReportTypeOutput, 0, buf, len);
-       if (ret == kIOReturnSuccess) return 1;
+
+       double start = CFAbsoluteTimeGetCurrent();
+       while (CFAbsoluteTimeGetCurrent() - timeout < start) {
+               ret = IOHIDDeviceSetReport(iokit_teensy_reference,
+                       kIOHIDReportTypeOutput, 0, buf, len);
+               if (ret == kIOReturnSuccess) return 1;
+               usleep(10000);
+       }
+
        return 0;
 }
 
@@ -634,6 +751,12 @@ int hard_reboot(void)
        return 0;
 }
 
+int soft_reboot(void)
+{
+       printf("Soft reboot is not implemented for OSX\n");
+       return 0;
+}
+
 #endif
 
 
@@ -657,11 +780,6 @@ int hard_reboot(void)
 #include <dev/usb/usb_ioctl.h>
 #endif
 
-#ifndef USB_GET_DEVICEINFO
-# define USB_GET_DEVICEINFO 0
-# error The USB_GET_DEVICEINFO ioctl() value is not defined for your system.
-#endif
-
 int open_usb_device(int vid, int pid)
 {
        int r, fd;
@@ -717,7 +835,7 @@ int teensy_write(void *buf, int len, double timeout)
 {
        int r;
 
-       // TODO: implement timeout... how??
+       // TODO: imeplement timeout... how??
        r = write(uhid_teensy_fd, buf, len);
        if (r == len) return 1;
        return 0;
@@ -748,6 +866,12 @@ int hard_reboot(void)
        return 0;
 }
 
+int soft_reboot(void)
+{
+       printf("Soft reboot is not implemented for UHID\n");
+       return 0;
+}
+
 #endif
 
 
@@ -761,7 +885,7 @@ int hard_reboot(void)
 // the maximum flash image size we can support
 // chips with larger memory may be used, but only this
 // much intel-hex data can be loaded into memory!
-#define MAX_MEMORY_SIZE 0x10000
+#define MAX_MEMORY_SIZE 0x100000
 
 static unsigned char firmware_image[MAX_MEMORY_SIZE];
 static unsigned char firmware_mask[MAX_MEMORY_SIZE];
@@ -795,8 +919,7 @@ int read_intel_hex(const char *filename)
                lineno++;
                if (*buf) {
                        if (parse_hex_line(buf) == 0) {
-                               //printf("Warning, parse error line %d\n", lineno);
-                               fclose(fp);
+                               printf("Warning, HEX parse error line %d\n", lineno);
                                return -2;
                        }
                }
@@ -812,7 +935,7 @@ int read_intel_hex(const char *filename)
 
 /* parses a line of intel hex code, stores the data in bytes[] */
 /* and the beginning address in addr, and returns a 1 if the */
-/* line was valid, or a 0 if an error occurred.  The variable */
+/* line was valid, or a 0 if an error occured.  The variable */
 /* num gets the number of bytes that were stored into bytes[] */
 
 
@@ -820,23 +943,23 @@ int
 parse_hex_line(char *line)
 {
        int addr, code, num;
-        int sum, len, cksum, i;
-        char *ptr;
-
-        num = 0;
-        if (line[0] != ':') return 0;
-        if (strlen(line) < 11) return 0;
-        ptr = line+1;
-        if (!sscanf(ptr, "%02x", &len)) return 0;
-        ptr += 2;
-        if ((int)strlen(line) < (11 + (len * 2)) ) return 0;
-        if (!sscanf(ptr, "%04x", &addr)) return 0;
-        ptr += 4;
-          /* printf("Line: length=%d Addr=%d\n", len, addr); */
-        if (!sscanf(ptr, "%02x", &code)) return 0;
+       int sum, len, cksum, i;
+       char *ptr;
+
+       num = 0;
+       if (line[0] != ':') return 0;
+       if (strlen(line) < 11) return 0;
+       ptr = line+1;
+       if (!sscanf(ptr, "%02x", &len)) return 0;
+       ptr += 2;
+       if ((int)strlen(line) < (11 + (len * 2)) ) return 0;
+       if (!sscanf(ptr, "%04x", &addr)) return 0;
+       ptr += 4;
+         /* printf("Line: length=%d Addr=%d\n", len, addr); */
+       if (!sscanf(ptr, "%02x", &code)) return 0;
        if (addr + extended_addr + len >= MAX_MEMORY_SIZE) return 0;
-        ptr += 2;
-        sum = (len & 255) + ((addr >> 8) & 255) + (addr & 255) + (code & 255);
+       ptr += 2;
+       sum = (len & 255) + ((addr >> 8) & 255) + (addr & 255) + (code & 255);
        if (code != 0) {
                if (code == 1) {
                        end_record_seen = 1;
@@ -846,7 +969,7 @@ parse_hex_line(char *line)
                        if (!sscanf(ptr, "%04x", &i)) return 1;
                        ptr += 4;
                        sum += ((i >> 8) & 255) + (i & 255);
-                       if (!sscanf(ptr, "%02x", &cksum)) return 1;
+                       if (!sscanf(ptr, "%02x", &cksum)) return 1;
                        if (((sum & 255) + (cksum & 255)) & 255) return 1;
                        extended_addr = i << 4;
                        //printf("ext addr = %05X\n", extended_addr);
@@ -855,27 +978,32 @@ parse_hex_line(char *line)
                        if (!sscanf(ptr, "%04x", &i)) return 1;
                        ptr += 4;
                        sum += ((i >> 8) & 255) + (i & 255);
-                       if (!sscanf(ptr, "%02x", &cksum)) return 1;
+                       if (!sscanf(ptr, "%02x", &cksum)) return 1;
                        if (((sum & 255) + (cksum & 255)) & 255) return 1;
                        extended_addr = i << 16;
+                       if (code_size > 1048576 && block_size >= 1024 &&
+                          extended_addr >= 0x60000000 && extended_addr < 0x60000000 + code_size) {
+                               // Teensy 4.0 HEX files have 0x60000000 FlexSPI offset
+                               extended_addr -= 0x60000000;
+                       }
                        //printf("ext addr = %08X\n", extended_addr);
                }
                return 1;       // non-data line
        }
        byte_count += len;
-        while (num != len) {
-                if (sscanf(ptr, "%02x", &i) != 1) return 0;
+       while (num != len) {
+               if (sscanf(ptr, "%02x", &i) != 1) return 0;
                i &= 255;
                firmware_image[addr + extended_addr + num] = i;
                firmware_mask[addr + extended_addr + num] = 1;
-                ptr += 2;
-                sum += i;
-                (num)++;
-                if (num >= 256) return 0;
-        }
-        if (!sscanf(ptr, "%02x", &cksum)) return 0;
-        if (((sum & 255) + (cksum & 255)) & 255) return 0; /* checksum error */
-        return 1;
+               ptr += 2;
+               sum += i;
+               (num)++;
+               if (num >= 256) return 0;
+       }
+       if (!sscanf(ptr, "%02x", &cksum)) return 0;
+       if (((sum & 255) + (cksum & 255)) & 255) return 0; /* checksum error */
+       return 1;
 }
 
 int ihex_bytes_within_range(int begin, int end)
@@ -912,6 +1040,21 @@ void ihex_get_data(int addr, int len, unsigned char *bytes)
        }
 }
 
+int memory_is_blank(int addr, int block_size)
+{
+       if (addr < 0 || addr > MAX_MEMORY_SIZE) return 1;
+
+       while (block_size && addr < MAX_MEMORY_SIZE) {
+               if (firmware_mask[addr] && firmware_image[addr] != 255) return 0;
+               addr++;
+               block_size--;
+       }
+       return 1;
+}
+
+
+
+
 /****************************************************************/
 /*                                                              */
 /*                       Misc Functions                         */
@@ -921,22 +1064,21 @@ void ihex_get_data(int addr, int len, unsigned char *bytes)
 int printf_verbose(const char *format, ...)
 {
        va_list ap;
-       int r = 0;
+       int r;
 
        va_start(ap, format);
        if (verbose) {
                r = vprintf(format, ap);
                fflush(stdout);
+               return r;
        }
-       va_end(ap);
-
-       return r;
+       return 0;
 }
 
 void delay(double seconds)
 {
-       #ifdef USE_WIN32
-       sleep(seconds * 1000.0);
+       #ifdef WIN32
+       Sleep(seconds * 1000.0);
        #else
        usleep(seconds * 1000000.0);
        #endif
@@ -949,65 +1091,146 @@ void die(const char *str, ...)
        va_start(ap, str);
        vfprintf(stderr, str, ap);
        fprintf(stderr, "\n");
-       va_end(ap);
-
        exit(1);
 }
 
-#if defined USE_WIN32
+#if defined(WIN32)
 #define strcasecmp stricmp
 #endif
 
+
+static const struct {
+       const char *name;
+       int code_size;
+       int block_size;
+} MCUs[] = {
+       {"at90usb162",   15872,   128},
+       {"atmega32u4",   32256,   128},
+       {"at90usb646",   64512,   256},
+       {"at90usb1286", 130048,   256},
+#if defined(USE_LIBUSB) || defined(USE_APPLE_IOKIT) || defined(USE_WIN32)
+       {"mkl26z64",     63488,   512},
+       {"mk20dx128",   131072,  1024},
+       {"mk20dx256",   262144,  1024},
+       {"mk66fx1m0",  1048576,  1024},
+       {"mk64fx512",   524288,  1024},
+       {"imxrt1062",  2031616,  1024},
+
+       // Add duplicates that match friendly Teensy Names
+       // Match board names in boards.txt
+       {"TEENSY2",     32256,   128},
+       {"TEENSY2PP",  130048,   256},
+       {"TEENSYLC",    63488,   512},
+       {"TEENSY30",   131072,  1024},
+       {"TEENSY31",   262144,  1024},
+       {"TEENSY32",   262144,  1024},
+       {"TEENSY35",   524288,  1024},
+       {"TEENSY36",  1048576,  1024},
+       {"TEENSY40",  2031616,  1024},
+       {"TEENSY41",  8126464,  1024},
+#endif
+       {NULL, 0, 0},
+};
+
+
+void list_mcus()
+{
+       int i;
+       printf("Supported MCUs are:\n");
+       for(i=0; MCUs[i].name != NULL; i++)
+               printf(" - %s\n", MCUs[i].name);
+       exit(1);
+}
+
+
+void read_mcu(char *name)
+{
+       int i;
+
+       if(name == NULL) {
+               fprintf(stderr, "No MCU specified.\n");
+               list_mcus();
+       }
+
+       for(i=0; MCUs[i].name != NULL; i++) {
+               if(strcasecmp(name, MCUs[i].name) == 0) {
+                       code_size  = MCUs[i].code_size;
+                       block_size = MCUs[i].block_size;
+                       return;
+               }
+       }
+
+       fprintf(stderr, "Unknown MCU type \"%s\"\n", name);
+       list_mcus();
+}
+
+
+void parse_flag(char *arg)
+{
+       int i;
+       for(i=1; arg[i]; i++) {
+               switch(arg[i]) {
+                       case 'w': wait_for_device_to_appear = 1; break;
+                       case 'r': hard_reboot_device = 1; break;
+                       case 's': soft_reboot_device = 1; break;
+                       case 'n': reboot_after_programming = 0; break;
+                       case 'v': verbose = 1; break;
+                       case 'b': boot_only = 1; break;
+                       default:
+                               fprintf(stderr, "Unknown flag '%c'\n\n", arg[i]);
+                               usage(NULL);
+               }
+       }
+}
+
+
 void parse_options(int argc, char **argv)
 {
        int i;
-       const char *arg;
+       char *arg;
 
        for (i=1; i<argc; i++) {
                arg = argv[i];
 
-               if (*arg == '-') {
-                       if (strcmp(arg, "-w") == 0) {
-                               wait_for_device_to_appear = 1;
-                       } else if (strcmp(arg, "-r") == 0) {
-                               hard_reboot_device = 1;
-                       } else if (strcmp(arg, "-n") == 0) {
-                               reboot_after_programming = 0;
-                       } else if (strcmp(arg, "-v") == 0) {
-                               verbose = 1;
-                       } else if (strncmp(arg, "-mmcu=", 6) == 0) {
-                               arg += 6;
-
-                               if (strncmp(arg, "at90usb", 7) == 0) {
-                                       arg += 7;
-                               } else if (strncmp(arg, "atmega", 6) == 0) {
-                                       arg += 6;
-                               } else {
-                                       die("Unknown MCU type\n");
+               //backward compatibility with previous versions.
+               if(strncmp(arg, "-mmcu=", 6) == 0) {
+                       read_mcu(strchr(arg, '=') + 1);
+               }
+
+               else if(arg[0] == '-') {
+                       if(arg[1] == '-') {
+                               char *name = &arg[2];
+                               char *val  = strchr(name, '=');
+                               if(val == NULL) {
+                                       //value must be the next string.
+                                       val = argv[++i];
+                               }
+                               else {
+                                       //we found an =, so split the string at it.
+                                       *val = '\0';
+                                        val = &val[1];
                                }
 
-                               if (strncmp(arg, "128", 3) == 0) {
-                                       code_size  = 128 * 1024;
-                                       block_size = 256;
-                               } else if (strncmp(arg, "64", 2) == 0) {
-                                       code_size  = 64 * 1024;
-                                       block_size = 256;
-                               } else if (strncmp(arg, "32", 2) == 0) {
-                                       code_size  = 32 * 1024;
-                                       block_size = 128;
-                               } else if (strncmp(arg, "16", 2) == 0) {
-                                       code_size  = 16 * 1024;
-                                       block_size = 128;
-                               } else if (strncmp(arg, "8", 1) == 0) {
-                                       code_size  = 8 * 1024;
-                                       block_size = 128;
-                               } else {
-                                       die("Unknown MCU type\n");
+                               if(strcasecmp(name, "help") == 0) usage(NULL);
+                               else if(strcasecmp(name, "mcu") == 0) read_mcu(val);
+                               else if(strcasecmp(name, "list-mcus") == 0) list_mcus();
+                               else {
+                                       fprintf(stderr, "Unknown option \"%s\"\n\n", arg);
+                                       usage(NULL);
                                }
                        }
-               } else {
-                       filename = argv[i];
+                       else parse_flag(arg);
                }
+               else filename = arg;
        }
 }
 
+void boot(unsigned char *buf, int write_size)
+{
+       printf_verbose("Booting\n");
+       memset(buf, 0, write_size);
+       buf[0] = 0xFF;
+       buf[1] = 0xFF;
+       buf[2] = 0xFF;
+       teensy_write(buf, write_size, 0.5);
+}