Add ability to access device using stdio_usb (#23)

* add -n option to load to avoid overwriting existing program

* Add -f/-F option to force reset a cooperative RP2040 board that isnt in BOOTSEL mode
diff --git a/CMakeLists.txt b/CMakeLists.txt
index fb72351..65ae8b5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,6 +2,10 @@
 
 project(picotool)
 
+if (NOT CMAKE_BUILD_TYPE)
+    set(CMAKE_BUILD_TYPE Release)
+endif()
+
 if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
     set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
     message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
@@ -32,13 +36,15 @@
     add_executable(picotool main.cpp)
     set(PICOTOOL_VERSION 1.1.0-develop)
     set(SYSTEM_VERSION "${CMAKE_SYSTEM_NAME} ${CMAKE_SYSTEM_VERSION}")
-    set(COMPILER_INFO "${CMAKE_C_COMPILER_ID}-${CMAKE_C_COMPILER_VERSION}-${CMAKE_BUILD_TYPE}")
+    set(COMPILER_INFO "${CMAKE_C_COMPILER_ID}-${CMAKE_C_COMPILER_VERSION}, ${CMAKE_BUILD_TYPE}")
     target_compile_definitions(picotool PRIVATE
             PICOTOOL_VERSION="${PICOTOOL_VERSION}"
             SYSTEM_VERSION="${SYSTEM_VERSION}"
             COMPILER_INFO="${COMPILER_INFO}"
             )
     target_include_directories(picotool PRIVATE ${LIBUSB_INCLUDE_DIR})
+    # todo, this is a bit of an abstraction failure; but don't want to rev the SDK just for this right now
+    target_include_directories(picotool PRIVATE ${PICO_SDK_PATH}/src/rp2_common/pico_stdio_usb/include)
     target_link_libraries(picotool pico_binary_info boot_uf2_headers boot_picoboot_headers pico_platform_headers picoboot_connection_cxx ${LIBUSB_LIBRARIES})
     # allow `make install`
     install(TARGETS picotool)
diff --git a/README.md b/README.md
index 982cadc..fc35840 100644
--- a/README.md
+++ b/README.md
@@ -36,25 +36,26 @@
     Tool for interacting with a RP2040 device in BOOTSEL mode, or with a RP2040 binary
 
 SYNOPSYS:
-    picotool info [-b] [-p] [-d] [-l] [-a] [--bus <bus>] [--address <addr>]
+    picotool info [-b] [-p] [-d] [-l] [-a] [--bus <bus>] [--address <addr>] [-f] [-F]
     picotool info [-b] [-p] [-d] [-l] [-a] <filename> [-t <type>]
-    picotool load [-v] [-r] <filename> [-t <type>] [--bus <bus>] [--address <addr>]
-    picotool save [-p] [--bus <bus>] [--address <addr>] <filename> [-t <type>]
-    picotool save -a [--bus <bus>] [--address <addr>] <filename> [-t <type>]
-    picotool save -r <from> <to> [--bus <bus>] [--address <addr>] <filename> [-t <type>]
-    picotool verify [--bus <bus>] [--address <addr>] <filename> [-t <type>] [-r <from> <to>]
-    picotool reboot [-a] [-u] [--bus <bus>] [--address <addr>]
+    picotool load [-v] [-x] <filename> [-t <type>] [-o <offset>] [--bus <bus>] [--address <addr>] [-f] [-F]
+    picotool save [-p] [--bus <bus>] [--address <addr>] [-f] [-F] <filename> [-t <type>]
+    picotool save -a [--bus <bus>] [--address <addr>] [-f] [-F] <filename> [-t <type>]
+    picotool save -r <from> <to> [--bus <bus>] [--address <addr>] [-f] [-F] <filename> [-t <type>]
+    picotool verify [--bus <bus>] [--address <addr>] [-f] [-F] <filename> [-t <type>] [-r <from> <to>] [-o <offset>]
+    picotool reboot [-a] [-u] [--bus <bus>] [--address <addr>] [-f] [-F]
+    picotool version [-s]
     picotool help [<cmd>]
 
 COMMANDS:
-    info     Display information from the target device(s) or file.
-             Without any arguments, this will display basic information for all connected RP2040 devices in
-             BOOTSEL mode
-    load     Load the program / memory range stored in a file onto the device.
-    save     Save the program / memory stored in flash on the device to a file.
-    verify   Check that the device contents match those in the file.
-    reboot   Reboot the device
-    help     Show general help or help for a specific command
+    info      Display information from the target device(s) or file.
+              Without any arguments, this will display basic information for all connected RP2040 devices in BOOTSEL mode
+    load      Load the program / memory range stored in a file onto the device.
+    save      Save the program / memory stored in flash on the device to a file.
+    verify    Check that the device contents match those in the file.
+    reboot    Reboot the device
+    version   Display picotool version
+    help      Show general help or help for a specific command
 
 Use "picotool help <cmd>" for more info
 ```
@@ -69,15 +70,14 @@
 The information can be either read from one or more connected RP2040 devices in BOOTSEL mode, or from 
 a file. This file can be an ELF, a UF2 or a BIN file.
 
-```asciidoc
+```text
 $ picotool help info
 INFO:
     Display information from the target device(s) or file.
-    Without any arguments, this will display basic information for all connected RP2040 devices in USB boot
-    mode
+    Without any arguments, this will display basic information for all connected RP2040 devices in BOOTSEL mode
 
 SYNOPSYS:
-    picotool info [-b] [-p] [-d] [-l] [-a] [--bus <bus>] [--address <addr>]
+    picotool info [-b] [-p] [-d] [-l] [-a] [--bus <bus>] [--address <addr>] [-f] [-F]
     picotool info [-b] [-p] [-d] [-l] [-a] <filename> [-t <type>]
 
 OPTIONS:
@@ -105,7 +105,6 @@
         -t <type>
             Specify file type (uf2 | elf | bin) explicitly, ignoring file extension
 ```
-
 e.g.
 
 ```text
@@ -247,11 +246,7 @@
 - program features - this is a list built from individual strings in the binary, that can be displayed (e.g. we will have one for UART stdio and one for USB stdio) in the SDK
 - build attributes - this is a similar list of strings, for things pertaining to the binary itself (e.g. Debug Build)
 
-Note it is my intention that things like MicroPython would include features for parts of the language/libraries they include.
-
-This might not be as a feature string per se, but could be another aggregating list (features/attributes are well known)
-but we can add another piece of binary info to name a list attribute that aggregates strings with a particular tag, so you
-could trivially add "MicroPython libraries:" etc to the `picotool` output without changing the tool itself. 
+The binary information is self-describing/extensible, so programs can include information picotool is not aware of (e.g. MicroPython includes a list of in-built libraries)
 
 ### Pins
 
@@ -389,11 +384,6 @@
 
 ## Additional binary information/picotool features
 
-### SDK version
-
-Should add this; git revision in general is hard since the user may not have the SDK checked out from git, so we'll have to stick
-a version number in a header
-
 ### Block devices
 
 MicroPython and CircuitPython, eventually the SDK and others may support one or more storage devices in flash. We already
@@ -433,7 +423,7 @@
 ### USB device descriptors
 
 Seems like tagging these might be nice (we just need to store the pointer to it assuming - as is often the case -
-the descriptor is just a linear chunk of memory) ... I assume there is a tool out there to prettyify such a thing if picotool dumps the descriptor
+the descriptor is just a linear chunk of memory) ... I assume there is a tool out there to prettify such a thing if picotool dumps the descriptor
 in binary.
 
 ### Issues
diff --git a/main.cpp b/main.cpp
index aa7790d..8212cf5 100644
--- a/main.cpp
+++ b/main.cpp
@@ -10,7 +10,7 @@
 
 #include "cli.h"
 #include "clipp/clipp.h"
-#include <signal.h>
+#include <csignal>
 #include <cstdio>
 #include <map>
 #include <iostream>
@@ -24,11 +24,18 @@
 #include <numeric>
 #include <memory>
 #include <functional>
+#include <ctime>
+
 #include "boot/uf2.h"
 #include "picoboot_connection_cxx.h"
 #include "pico/binary_info.h"
+#include "pico/stdio_usb/reset_interface.h"
 #include "elf.h"
 
+#if defined(__unix__) || defined(__APPLE__)
+#include <unistd.h>
+#endif
+
 // tsk namespace is polluted on windows
 #ifdef _MSC_VER
 #undef min
@@ -59,7 +66,6 @@
 typedef map<enum picoboot_device_result,vector<pair<libusb_device *, libusb_device_handle *>>> device_map;
 
 typedef unsigned int uint;
-static libusb_context *ctx;
 
 auto memory_names = map<enum memory_type, string>{
         {memory_type::sram, "RAM"},
@@ -140,8 +146,25 @@
         to = other.clamp(to);
     }
 
+    bool intersects(const range& other) const {
+        return !(other.from >= to || other.to < from);
+    }
+
 };
 
+static void __noreturn fail(int code, string msg) {
+    throw command_failure(code, std::move(msg));
+}
+
+static void __noreturn fail(int code, const char *format, ...) {
+    va_list args;
+    va_start(args, format);
+    static char error_msg[512];
+    vsnprintf(error_msg, sizeof(error_msg), format, args);
+    va_end(args);
+    fail(code, string(error_msg));
+}
+
 // ranges should not overlap
 template <typename T> struct range_map {
     struct mapping {
@@ -150,19 +173,19 @@
         const uint32_t max_offset;
     };
 
-    void check_overlap(uint32_t p) {
-        auto f = m.lower_bound(p);
-        if (f != m.end()) {
-            assert(p >= f->first);
-            assert(p < f->second.first);
-        }
-    }
-
     void insert(const range& r, T t) {
         if (r.to != r.from) {
             assert(r.to > r.from);
-            check_overlap(r.from);
-            check_overlap(r.to);
+            // check we don't overlap any existing map entries
+            auto f = m.lower_bound(r.to); // f is first element that starts after or on r.to
+            if (f != m.begin()) {
+                f--;
+            }
+            if (f != m.end()) {
+                // due to edge cases above, f is either the entry before
+                // or after r, so just check for any overlap
+                fail(ERROR_FORMAT, "Found overlapping memory ranges 0x%08x->0x%08x and 0x%08x->%08x\n", f->first, f->second.first, r.from, r.to);
+            }
             m.insert(std::make_pair(r.from, std::make_pair(r.to, t)));
         }
     }
@@ -213,11 +236,13 @@
 
 struct cmd {
     explicit cmd(string name) : _name(std::move(name)) {}
-    enum device_support { none, one, zero_or_more, one_or_more };
+    enum device_support { none, one, zero_or_more };
     virtual group get_cli() = 0;
     virtual string get_doc() const = 0;
     virtual device_support get_device_support() { return one; }
-    virtual void execute(device_map& devices) = 0;
+    virtual bool force_requires_pre_reboot() { return true; }
+    // return true if the command caused a reboot
+    virtual bool execute(device_map& devices) = 0;
     const string& name() { return _name; }
 private:
     string _name;
@@ -235,7 +260,9 @@
     bool offset_set = false;
     bool range_set = false;
     bool reboot_usb = false;
+    bool reboot_app_specified = false;
     bool force = false;
+    bool force_no_reboot = false;
 
     struct {
         bool show_basic = false;
@@ -248,6 +275,8 @@
     struct {
         bool verify = false;
         bool execute = false;
+        bool no_overwrite = false;
+        bool no_overwrite_force = false;
     } load;
 
     struct {
@@ -267,6 +296,10 @@
             .if_missing([] { return "missing bus number"; })) % "Filter devices by USB bus number" +
         (option("--address") & integer("addr").min_value(1).max_value(127).set(settings.address)
             .if_missing([] { return "missing address"; })) % "Filter devices by USB device address"
+#if !defined(_WIN32)
+        + option('f', "--force").set(settings.force) % "Force a device not in BOOTSEL mode but running compatible code to reset so the command can be executed. After executing the command (unless the command itself is a 'reboot') the device will be rebooted back to application mode" +
+                option('F', "--force-no-reboot").set(settings.force_no_reboot) % "Force a device not in BOOTSEL mode but running compatible code to reset so the command can be executed. After executing the command (unless the command itself is a 'reboot') the device will be left connected and accessible to picotool, but without the RPI-RP2 drive mounted"
+#endif
     ).min(0).doc_non_optional(true);
 
 auto file_types = (option ('t', "--type") & value("type").set(settings.file_type))
@@ -282,7 +315,7 @@
 
 struct info_command : public cmd {
     info_command() : cmd("info") {}
-    void execute(device_map& devices) override;
+    bool execute(device_map& devices) override;
     device_support get_device_support() override {
         if (settings.filename.empty())
             return zero_or_more;
@@ -313,7 +346,7 @@
 
 struct verify_command : public cmd {
     verify_command() : cmd("verify") {}
-    void execute(device_map &devices) override;
+    bool execute(device_map &devices) override;
 
     group get_cli() override {
         return (
@@ -336,7 +369,7 @@
 
 struct save_command : public cmd {
     save_command() : cmd("save") {}
-    void execute(device_map &devices) override;
+    bool execute(device_map &devices) override;
 
     group get_cli() override {
         return (
@@ -362,11 +395,13 @@
 
 struct load_command : public cmd {
     load_command() : cmd("load") {}
-    void execute(device_map &devices) override;
+    bool execute(device_map &devices) override;
 
     group get_cli() override {
         return (
             (
+                option('n', "--no-overwrite").set(settings.load.no_overwrite) % "When writing flash data, do not overwrite an existing program in flash. If picotool cannot determine the size/presence of the program in flash, the command fails" +
+                option('N', "--no-overwrite-unsafe").set(settings.load.no_overwrite_force) % "When writing flash data, do not overwrite an existing program in flash. If picotool cannot determine the size/presence of the program in flash, the load continues anyway" +
                 option('v', "--verify").set(settings.load.verify) % "Verify the data was written correctly" +
                 option('x', "--execute").set(settings.load.execute) % "Attempt to execute the downloaded file as a program after the load"
             ).min(0).doc_non_optional(true) % "Post load actions" +
@@ -386,7 +421,7 @@
 
 struct help_command : public cmd {
     help_command() : cmd("help") {}
-    void execute(device_map &devices) override;
+    bool execute(device_map &devices) override;
 
     device_support get_device_support() override {
         return device_support::none;
@@ -405,11 +440,12 @@
 
 struct version_command : public cmd {
     version_command() : cmd("version") {}
-    void execute(device_map &devices) override {
+    bool execute(device_map &devices) override {
         if (settings.version.semantic)
             std::cout << PICOTOOL_VERSION << "\n";
         else
-            std::cout << "picotool v" << PICOTOOL_VERSION << " (" << SYSTEM_VERSION << ":" << COMPILER_INFO << ")\n";
+            std::cout << "picotool v" << PICOTOOL_VERSION << " (" << SYSTEM_VERSION << ", " << COMPILER_INFO << ")\n";
+        return false;
     }
 
     device_support get_device_support() override {
@@ -428,18 +464,27 @@
 } version_cmd;
 
 struct reboot_command : public cmd {
+    bool quiet;
     reboot_command() : cmd("reboot") {}
-    void execute(device_map &devices) override;
+    bool execute(device_map &devices) override;
 
     group get_cli() override {
         return
         (
-            option('a', "--application").clear(settings.reboot_usb) % "Reboot back into the application (this is the default)" +
+            option('a', "--application").set(settings.reboot_app_specified) % "Reboot back into the application (this is the default)" +
             option('u', "--usb").set(settings.reboot_usb) % "Reboot back into BOOTSEL mode "
+#if defined(_WIN32)
+            + option('f', "--force").set(settings.force) % "Force a device not in BOOTSEL mode but running compatible code to reboot."
+#endif
         ).min(0).doc_non_optional(true) % "Reboot type" +
         device_selection % "Selecting the device to reboot";
     }
 
+    bool force_requires_pre_reboot() override {
+        // no point in rebooting twice
+        return false;
+    }
+
     string get_doc() const override {
         return "Reboot the device";
     }
@@ -473,6 +518,13 @@
 
 clipp::formatting_ostream<std::ostream> fos(std::cout);
 
+static void sleep_ms(int ms) {
+#if defined(__unix__) || defined(__APPLE__)
+    usleep(ms * 1000);
+#else
+    Sleep(ms);
+#endif
+}
 using cli::option;
 using cli::integer;
 int parse(const int argc, char **argv) {
@@ -609,7 +661,8 @@
         args.erase(args.begin()); // remove the cmd itself
         cli::match(settings, selected_cmd->get_cli(), args);
     } catch (std::exception &e) {
-        std::cout << "ERROR: " << e.what() << "\n\n";
+        fos.wrap_hard();
+        fos << "ERROR: " << e.what() << "\n\n";
         usage();
         return ERROR_ARGS;
     }
@@ -695,10 +748,6 @@
     }
 };
 
-static void __noreturn fail(int code, string msg) {
-    throw command_failure(code, msg);
-}
-
 uint32_t bootrom_func_lookup(memory_access& access, uint16_t tag) {
     auto magic = access.read_int(BOOTROM_MAGIC_ADDR);
     magic &= 0xffffff; // ignore bootrom version
@@ -891,16 +940,6 @@
     }
 }
 
-static char error_msg[512];
-
-static void __noreturn fail(int code, const char *format, ...) {
-    va_list args;
-    va_start(args, format);
-    vsnprintf(error_msg, sizeof(error_msg), format, args);
-    va_end(args);
-    fail(code, string(error_msg));
-}
-
 static void __noreturn fail_read_error() {
     fail(ERROR_READ_FAILED, "Failed to read input file");
 }
@@ -963,6 +1002,12 @@
 }
 
 struct bi_visitor_base {
+    void visit(memory_access& access, const binary_info_header& hdr) {
+        for (const auto &a : hdr.bi_addr) {
+            visit(access, a);
+        }
+    }
+
     void visit(memory_access& access, uint32_t addr) {
         binary_info_core_t bi;
         access.read_raw(addr, bi);
@@ -1364,9 +1409,7 @@
                 named_feature_groups[std::make_pair(group_tag, group_id)] = std::make_pair(label, flags);
             });
 
-            for (const auto &a : hdr.bi_addr) {
-                visitor.visit(access, a);
-            }
+            visitor.visit(access, hdr);
 
             visitor = bi_visitor{};
             visitor.id_and_int([&](int tag, uint32_t id, uint32_t value) {
@@ -1412,9 +1455,7 @@
                 }
             });
 
-            for (const auto &a : hdr.bi_addr) {
-                visitor.visit(access, a);
-            }
+            visitor.visit(access, hdr);
 
             if (settings.info.show_basic || settings.info.all) {
                 select_group(program_info);
@@ -1521,62 +1562,70 @@
     }
 }
 
-string missing_device_string() {
-    char buf[256];
+string missing_device_string(bool wasRetry) {
+    char b[256];
+    if (wasRetry) {
+        strcpy(b, "Despite the reboot attempt, no ");
+    } else {
+        strcpy(b, "No ");
+    }
+    char *buf = b + strlen(b);
     if (settings.address != -1) {
         if (settings.bus != -1) {
-            sprintf(buf, "No accessible RP2040 device in BOOTSEL mode was found at bus %d, address %d.", settings.bus, settings.address);
+            sprintf(buf, "accessible RP2040 device in BOOTSEL mode was found at bus %d, address %d.", settings.bus, settings.address);
         } else {
-            sprintf(buf, "No accessible RP2040 devices in BOOTSEL mode were found with address %d.", settings.address);
+            sprintf(buf, "accessible RP2040 devices in BOOTSEL mode were found with address %d.", settings.address);
         }
     } else {
         if (settings.bus != -1) {
-            sprintf(buf, "No accessible RP2040 devices in BOOTSEL mode were found found on bus %d.", settings.bus);
+            sprintf(buf, "accessible RP2040 devices in BOOTSEL mode were found found on bus %d.", settings.bus);
         } else {
-            sprintf(buf, "No accessible RP2040 devices in BOOTSEL mode were found.");
+            sprintf(buf, "accessible RP2040 devices in BOOTSEL mode were found.");
         }
     }
-    return buf;
+    return b;
 }
 
-void help_command::execute(device_map &devices) {
+bool help_command::execute(device_map &devices) {
     assert(false);
+    return false;
 }
 
-void info_command::execute(device_map &devices) {
+bool info_command::execute(device_map &devices) {
+    fos.first_column(0); fos.hanging_indent(0);
     if (!settings.filename.empty()) {
         auto access = get_file_memory_access();
-        std::cout << "File " << settings.filename << ":\n\n";
+        fos << "File " << settings.filename << ":\n\n";
         info_guts(access);
-        return;
+        return false;
     }
     int size = devices[dr_vidpid_bootrom_ok].size();
     if (size) {
         if (size > 1) {
-            std::cout << "Multiple RP2040 devices in BOOTSEL mode found:\n";
+            fos << "Multiple RP2040 devices in BOOTSEL mode found:\n";
         }
         for (auto handles : devices[dr_vidpid_bootrom_ok]) {
+            fos.first_column(0); fos.hanging_indent(0);
             if (size > 1) {
                 auto s = bus_device_string(handles.first);
                 string dashes;
                 std::generate_n(std::back_inserter(dashes), s.length() + 1, [] { return '-'; });
-                std::cout << "\n" << s << ":\n" << dashes << "\n";
+                fos << "\n" << s << ":\n" << dashes << "\n";
             }
             picoboot::connection connection(handles.second);
             picoboot_memory_access access(connection);
             info_guts(access);
         }
     } else {
-        fail(ERROR_NO_DEVICE, missing_device_string());
+        fail(ERROR_NO_DEVICE, missing_device_string(false));
     }
+    return false;
 }
 
-static picoboot::connection get_single_usb_boot_device(device_map& devices, bool exclusive = true) {
-    if (devices[dr_vidpid_bootrom_ok].size() != 1) {
-        fail(ERROR_NOT_POSSIBLE, "Command requires a single RP2040 device to be targeted.");
-    }
+static picoboot::connection get_single_bootsel_device_connection(device_map& devices, bool exclusive = true) {
+    assert(devices[dr_vidpid_bootrom_ok].size() == 1);
     libusb_device_handle *rc = devices[dr_vidpid_bootrom_ok][0].second;
-    if (!rc) fail(ERROR_USB, "Unabled to connect to device");
+    if (!rc) fail(ERROR_USB, "Unable to connect to device");
     return picoboot::connection(rc, exclusive);
 }
 
@@ -1606,8 +1655,8 @@
     int width;
 };
 
-void save_command::execute(device_map &devices) {
-    auto con = get_single_usb_boot_device(devices);
+bool save_command::execute(device_map &devices) {
+    auto con = get_single_bootsel_device_connection(devices);
     picoboot_memory_access raw_access(con);
 
     uint32_t end = 0;
@@ -1635,9 +1684,7 @@
                         return;
                     if (id == BINARY_INFO_ID_RP_BINARY_END) binary_end = value;
                 });
-                for (const auto &a : hdr.bi_addr) {
-                    visitor.visit(access, a);
-                }
+                visitor.visit(access, hdr);
             }
             if (binary_end == 0) {
                 fail(ERROR_NOT_POSSIBLE,
@@ -1722,6 +1769,7 @@
             throw;
         }
     }
+    return false;
 }
 
 vector<range> get_colaesced_ranges(file_memory_access &file_access) {
@@ -1744,13 +1792,32 @@
     return ranges;
 }
 
-void load_command::execute(device_map &devices) {
+bool load_command::execute(device_map &devices) {
     if (settings.offset_set && get_file_type() != filetype::bin) {
         fail(ERROR_ARGS, "Offset only valid for BIN files");
     }
     auto file_access = get_file_memory_access();
-    auto con = get_single_usb_boot_device(devices);
+    auto con = get_single_bootsel_device_connection(devices);
     picoboot_memory_access raw_access(con);
+    range flash_binary_range(FLASH_START, FLASH_END);
+    bool flash_binary_end_unknown = true;
+    if (settings.load.no_overwrite_force) settings.load.no_overwrite = true;
+    if (settings.load.no_overwrite) {
+        binary_info_header hdr;
+        if (find_binary_info(raw_access, hdr)) {
+            auto access = remapped_memory_access(raw_access, hdr.reverse_copy_mapping);
+            auto visitor = bi_visitor{};
+            visitor.id_and_int([&](int tag, uint32_t id, uint32_t value) {
+                if (tag != BINARY_INFO_TAG_RASPBERRY_PI)
+                    return;
+                if (id == BINARY_INFO_ID_RP_BINARY_END) {
+                    flash_binary_range.to = value;
+                    flash_binary_end_unknown = false;
+                }
+            });
+            visitor.visit(access, hdr);
+        }
+    }
     auto ranges = get_colaesced_ranges(file_access);
     for (auto mem_range : ranges) {
         enum memory_type t1 = get_memory_type(mem_range.from);
@@ -1759,6 +1826,16 @@
             fail(ERROR_FORMAT, "File to load contained an invalid memory range 0x%08x-0x%08x", mem_range.from,
                  mem_range.to);
         }
+        if (settings.load.no_overwrite && mem_range.intersects(flash_binary_range)) {
+            if (flash_binary_end_unknown) {
+                if (!settings.load.no_overwrite_force) {
+                    fail(ERROR_NOT_POSSIBLE, "-n option specified, but the size/presence of an existing flash binary could not be detected; aborting. Consider using the -N option");
+                }
+            } else {
+                fail(ERROR_NOT_POSSIBLE, "-n option specified, and the loaded data range clashes with the existing flash binary range %08x->%08x",
+                     flash_binary_range.from, flash_binary_range.to);
+            }
+        }
     }
     for (auto mem_range : ranges) {
         enum memory_type type = get_memory_type(mem_range.from);
@@ -1833,15 +1910,18 @@
             fail(ERROR_FORMAT, "Cannot execute as file does not contain a valid RP2 executable image");
         }
         con.reboot(flash == get_memory_type(start) ? 0 : start, SRAM_END, 500);
+        std::cout << "\nThe device was rebooted to start the application.\n";
+        return true;
     }
+    return false;
 }
 
-void verify_command::execute(device_map &devices) {
+bool verify_command::execute(device_map &devices) {
     if (settings.offset_set && get_file_type() != filetype::bin) {
         fail(ERROR_ARGS, "Offset only valid for BIN files");
     }
     auto file_access = get_file_memory_access();
-    auto con = get_single_usb_boot_device(devices);
+    auto con = get_single_bootsel_device_connection(devices);
     picoboot_memory_access raw_access(con);
     auto ranges = get_colaesced_ranges(file_access);
     if (settings.range_set) {
@@ -1938,36 +2018,94 @@
             }
         }
     }
+    return false;
 }
 
-void reboot_command::execute(device_map &devices) {
-    // not exclusive, because restoring un-exclusive could fail; also if we're rebooting, we don't much
-    // care what else is happening.
-    auto con = get_single_usb_boot_device(devices, false);
-    if (!settings.reboot_usb) {
-        con.reboot(0, SRAM_END, 500);
-        std::cout << "The device was rebooted.\n";
-    } else {
-        picoboot_memory_access raw_access(con);
-        uint program_base = SRAM_START;
-        std::vector<uint32_t> program = {
-                0x20002100, // movs r0, #0;       movs r1, #0
-                0x47104a00, // ldr  r2, [pc, #0]; bx r2
-                bootrom_func_lookup(raw_access, rom_table_code('U','B'))
-        };
-
-        raw_access.write_vector(program_base, program);
-        try {
-            con.exec(program_base);
-        } catch (picoboot::connection_error &e) {
-            if (e.libusb_code == LIBUSB_ERROR_NO_DEVICE) {
-                // not unreasonable once it reboots
-                return;
-            }
-            throw e;
-        }
-        fail(ERROR_NOT_POSSIBLE, "Reboot into USB boot not implemented yet");
+static int reboot_device(libusb_device *device, bool bootsel, uint disable_mask=0) {
+    // ok, the device isn't in USB boot mode, let's try to reboot via vendor interface
+    struct libusb_config_descriptor *config;
+    int ret = libusb_get_active_config_descriptor(device, &config);
+    if (ret) {
+        fail(ERROR_USB, "Failed to get descriptor %d\n", ret);
     }
+    libusb_device_handle *dev_handle;
+    ret = libusb_open(device, &dev_handle);
+    if (ret) {
+#if _MSC_VER
+        fail(ERROR_USB, "Unable to access device to reboot it; Make sure there is a driver installed via Zadig\n", ret);
+#else
+        fail(ERROR_USB, "Unable to access device to reboot it; Use sudo or setup a udev rule\n", ret);
+#endif
+    }
+    for (int i = 0; i < config->bNumInterfaces; i++) {
+        if (0xff == config->interface[i].altsetting[0].bInterfaceClass &&
+            RESET_INTERFACE_SUBCLASS == config->interface[i].altsetting[0].bInterfaceSubClass &&
+            RESET_INTERFACE_PROTOCOL == config->interface[i].altsetting[0].bInterfaceProtocol) {
+            ret = libusb_claim_interface(dev_handle, i);
+            if (ret) {
+                fail(ERROR_USB, "Failed to claim interface\n");
+            }
+            if (bootsel) {
+                ret = libusb_control_transfer(dev_handle, LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE,
+                                              RESET_REQUEST_BOOTSEL, disable_mask, i, nullptr, 0, 2000);
+            } else {
+                ret = libusb_control_transfer(dev_handle, LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE,
+                                              RESET_REQUEST_FLASH, 0, i, nullptr, 0, 2000);
+            }
+//            if (ret != 0 ) {
+//                fail(ERROR_UNKNOWN, "Unable to reset the device %d\n", ret);
+//            }
+            return 0;
+//            return ret;
+        }
+    }
+    fail(ERROR_USB, "Unable to locate reset interface on the device");
+}
+
+bool reboot_command::execute(device_map &devices) {
+    if (settings.force) {
+        reboot_device(devices[dr_vidpid_stdio_usb][0].first, settings.reboot_usb);
+        if (!quiet) {
+            if (settings.reboot_usb) {
+                std::cout << "The device was asked to reboot into BOOTSEL mode.\n";
+            } else {
+                std::cout << "The device was asked to reboot into application mode.\n";
+            }
+        }
+    } else {
+        // not exclusive, because restoring un-exclusive could fail; also if we're rebooting, we don't much
+        // care what else is happening.
+        auto con = get_single_bootsel_device_connection(devices, false);
+        if (!settings.reboot_usb) {
+            con.reboot(0, SRAM_END, 500);
+        } else {
+            picoboot_memory_access raw_access(con);
+            uint program_base = SRAM_START;
+            std::vector<uint32_t> program = {
+                    0x20002100, // movs r0, #0;       movs r1, #0
+                    0x47104a00, // ldr  r2, [pc, #0]; bx r2
+                    bootrom_func_lookup(raw_access, rom_table_code('U', 'B'))
+            };
+
+            raw_access.write_vector(program_base, program);
+            try {
+                con.exec(program_base);
+            } catch (picoboot::connection_error &e) {
+                // the reset_usb_boot above has a very short delay, so it frequently causes libusb to return
+                // fairly unpredictable errors... i think it is best to ignore them, because catching a rare
+                // case where the reboot command fails, is probably less important than potentially confusing
+                // the user with spurious error messages
+            }
+        }
+        if (!quiet) {
+            if (settings.reboot_usb) {
+                std::cout << "The device was rebooted into BOOTSEL mode.\n";
+            } else {
+                std::cout << "The device was rebooted into application mode.\n";
+            }
+        }
+    }
+    return true;
 }
 
 #if defined(_WIN32)
@@ -1997,6 +2135,8 @@
 }
 
 int main(int argc, char **argv) {
+    libusb_context *ctx = nullptr;
+
     int tw=0, th=0;
     get_terminal_size(tw, th);
     if (tw) {
@@ -2009,6 +2149,9 @@
         return 0;
     }
 
+    // save complicating the grammar
+    if (settings.force_no_reboot) settings.force = true;
+
     struct libusb_device **devs = nullptr;
     device_map devices;
     vector<libusb_device_handle *> to_close;
@@ -2016,58 +2159,143 @@
     try {
         signal(SIGINT, cancelled);
         signal(SIGTERM, cancelled);
+
+        if (settings.reboot_usb && settings.reboot_app_specified) {
+            fail(ERROR_ARGS, "Cannot specify both -u and -a reboot options");
+        }
+
         if (selected_cmd->get_device_support() != cmd::none) {
             if (libusb_init(&ctx)) {
                 fail(ERROR_USB, "Failed to initialise libUSB\n");
             }
-            rc = libusb_get_device_list(ctx, &devs);
-            if (rc < 0) {
-                fail(ERROR_USB, "Failed to enumerate USB devices\n");
-            } else {
-                rc = 0;
-            }
-            for (libusb_device **dev = devs; *dev; dev++) {
-                if (settings.bus != -1 && settings.bus != libusb_get_bus_number(*dev)) continue;
-                if (settings.address != -1 && settings.address != libusb_get_device_address(*dev)) continue;
-                libusb_device_handle *handle = nullptr;
-                auto result = picoboot_open_device(*dev, &handle);
-                if (handle) {
-                    to_close.push_back(handle);
-                }
-                if (result != dr_error) {
-                    devices[result].push_back(std::make_pair(*dev, handle));
-                }
-            }
         }
 
-        if (!rc) {
-            switch (selected_cmd->get_device_support()) {
+        // we only loop a second time if we want to reboot some devices (which may cause device
+        for (int tries = 0; !rc && tries < 2; tries++) {
+            if (ctx) {
+                if (libusb_get_device_list(ctx, &devs) < 0) {
+                    fail(ERROR_USB, "Failed to enumerate USB devices\n");
+                }
+                for (libusb_device **dev = devs; *dev; dev++) {
+                    if (settings.bus != -1 && settings.bus != libusb_get_bus_number(*dev)) continue;
+                    if (settings.address != -1 && settings.address != libusb_get_device_address(*dev)) continue;
+                    libusb_device_handle *handle = nullptr;
+                    auto result = picoboot_open_device(*dev, &handle);
+                    if (handle) {
+                        to_close.push_back(handle);
+                    }
+                    if (result != dr_error) {
+                        devices[result].push_back(std::make_pair(*dev, handle));
+                    }
+                }
+            }
+            auto supported = selected_cmd->get_device_support();
+            switch (supported) {
                 case cmd::device_support::zero_or_more:
                     if (!settings.filename.empty()) break;
                     // fall thru
                 case cmd::device_support::one:
-                case cmd::device_support::one_or_more:
-                    if (devices[dr_vidpid_bootrom_ok].empty()) {
-                        std::cout << missing_device_string() << "\n";
+                    if (devices[dr_vidpid_bootrom_ok].empty() &&
+                        (!settings.force || devices[dr_vidpid_stdio_usb].empty())) {
                         bool had_note = false;
-                        auto printer = [&](enum picoboot_device_result r, const string& description) {
+                        fos << missing_device_string(tries>0);
+                        if (tries > 0) {
+                            fos << " It is possible the device is not responding, and will have to be manually entered into BOOTSEL mode.\n";
+                            had_note = true; // suppress "but:" in this case
+                        }
+                        fos << "\n";
+                        fos.first_column(0);
+                        fos.hanging_indent(4);
+                        auto printer = [&](enum picoboot_device_result r, const string &description) {
                             if (!had_note && !devices[r].empty()) {
-                                std::cout << "\nbut:\n\n";
+                                fos << "\nbut:\n\n";
                                 had_note = true;
                             }
                             for (auto d : devices[r]) {
-                                std::cout << bus_device_string(d.first) << description << "\n";
+                                fos << bus_device_string(d.first) << description << "\n";
                             }
                         };
-                        printer(dr_vidpid_bootrom_cant_connect, " appears to be a RP2040 device in BOOTSEL mode, but picotool was unable to connect");
-                        printer(dr_vidpid_picoprobe, " appears to be a RP2040 PicoProbe device not in BOOTSEL mode.");
-                        printer(dr_vidpid_micropython, " appears to be a RP2040 MicroPython device not in BOOTSEL mode.");
+                        printer(dr_vidpid_bootrom_cant_connect,
+                                " appears to be a RP2040 device in BOOTSEL mode, but picotool was unable to connect");
+                        printer(dr_vidpid_picoprobe,
+                                " appears to be a RP2040 PicoProbe device not in BOOTSEL mode.");
+                        printer(dr_vidpid_micropython,
+                                " appears to be a RP2040 MicroPython device not in BOOTSEL mode.");
+                        if (selected_cmd->force_requires_pre_reboot()) {
+#if defined(_WIN32)
+                            printer(dr_vidpid_stdio_usb,
+                                    " appears to be a RP2040 device with a USB serial connection, not in BOOTSEL mode. You can force reboot into BOOTSEL mode via 'picotool reboot -f -u' first.");
+#else
+                            printer(dr_vidpid_stdio_usb,
+                                    " appears to be a RP2040 device with a USB serial connection, so consider -f (or -F) to force reboot in order to run the command.");
+#endif
+                        } else {
+                            // special case message for what is actually just reboot (the only command that doesn't require reboot first)
+                            printer(dr_vidpid_stdio_usb,
+                                    " appears to be a RP2040 device with a USB serial connection, so consider -f to force the reboot.");
+                        }
                         rc = ERROR_NO_DEVICE;
+                    } else if (supported == cmd::device_support::one) {
+                        if (devices[dr_vidpid_bootrom_ok].size() > 1 ||
+                            (devices[dr_vidpid_bootrom_ok].empty() && devices[dr_vidpid_stdio_usb].size() > 1)) {
+                            fail(ERROR_NOT_POSSIBLE, "Command requires a single RP2040 device to be targeted.");
+                        }
+                        if (!devices[dr_vidpid_bootrom_ok].empty()) {
+                            settings.force = false; // we have a device, so we're not forcing
+                        }
+                    } else if (supported == cmd::device_support::zero_or_more && settings.force && !devices[dr_vidpid_bootrom_ok].empty()) {
+                        // we have usable devices, so lets use them without force
+                        settings.force = false;
                     }
+                    fos.first_column(0);
+                    fos.hanging_indent(0);
+                    break;
                 default:
                     break;
             }
-            if (!rc) selected_cmd->execute(devices);
+            if (!rc) {
+                if (settings.force && ctx) { // actually ctx should never be null as we are targeting device if force is set, but still
+                    if (devices[dr_vidpid_stdio_usb].size() != 1) {
+                        fail(ERROR_NOT_POSSIBLE,
+                             "Forced command requires a single rebootable RP2040 device to be targeted.");
+                    }
+                    if (selected_cmd->force_requires_pre_reboot()) {
+                        // we reboot into BOOTSEL mode and disable MSC interface (the 1 here)
+                        auto &to_reboot = devices[dr_vidpid_stdio_usb][0].first;
+                        reboot_device(to_reboot, true, 1);
+                        fos << "The device was asked to reboot into BOOTSEL mode so the command can be executed.\n\n";
+                        for (const auto &handle : to_close) {
+                            libusb_close(handle);
+                        }
+                        libusb_free_device_list(devs, 1);
+                        devs = nullptr;
+                        to_close.clear();
+                        devices.clear();
+                        sleep_ms(1200);
+
+                        // we now clear settings.force, because we expect the device to have rebooted and be available.
+                        // we also clear any filters, because the device may have moved, so the only way we can find it
+                        // again is to assume it is the only now visible device.
+                        settings.force = false;
+                        settings.address = -1;
+                        settings.bus = -1;
+                        continue;
+                    }
+                }
+                if (!selected_cmd->execute(devices) && tries) {
+                    if (settings.force_no_reboot) {
+                        fos << "\nThe device has been left accessible, but without the drive mounted; use 'picotool reboot' to reboot into regular BOOTSEL mode or application mode.\n";
+                    } else {
+                        // can only really do this with one device
+                        if (devices[dr_vidpid_bootrom_ok].size() == 1) {
+                            reboot_cmd.quiet = true;
+                            reboot_cmd.execute(devices);
+                            fos << "\nThe device was asked to reboot back into application mode.\n";
+                        }
+                    }
+                }
+                break;
+            }
         }
     } catch (command_failure &e) {
         std::cout << "ERROR: " << e.what() << "\n";
diff --git a/picoboot_connection/picoboot_connection.c b/picoboot_connection/picoboot_connection.c
index 198c529..d1f2bee 100644
--- a/picoboot_connection/picoboot_connection.c
+++ b/picoboot_connection/picoboot_connection.c
@@ -24,6 +24,7 @@
 #define PRODUCT_ID_RP2_USBBOOT 0x0003u
 #define PRODUCT_ID_PICOPROBE   0x0004u
 #define PRODUCT_ID_MICROPYTHON 0x0005u
+#define PRODUCT_ID_STDIO_USB   0x000au
 
 uint32_t crc32_for_byte(uint32_t remainder) {
     const uint32_t POLYNOMIAL = 0x4C11DB7;
@@ -72,6 +73,8 @@
                 return dr_vidpid_micropython;
             case PRODUCT_ID_PICOPROBE:
                 return dr_vidpid_picoprobe;
+            case PRODUCT_ID_STDIO_USB:
+                return dr_vidpid_stdio_usb;
             case PRODUCT_ID_RP2_USBBOOT:
                 break;
             default:
diff --git a/picoboot_connection/picoboot_connection.h b/picoboot_connection/picoboot_connection.h
index 796b57c..ebdabfc 100644
--- a/picoboot_connection/picoboot_connection.h
+++ b/picoboot_connection/picoboot_connection.h
@@ -9,6 +9,7 @@
 
 // todo we should use fully encapsulate libusb
 
+#include <assert.h>
 #include <libusb.h>
 #include "boot/picoboot.h"
 
@@ -24,6 +25,7 @@
     dr_vidpid_picoprobe,
     dr_vidpid_unknown,
     dr_error,
+    dr_vidpid_stdio_usb,
 };
 
 enum picoboot_device_result picoboot_open_device(libusb_device *device, libusb_device_handle **dev_handle);