From 55fd880c3dc029b961fc1a0967a6cfdc0af02721 Mon Sep 17 00:00:00 2001 From: Graham Sanderson Date: Mon, 1 Nov 2021 14:37:01 -0500 Subject: [PATCH] V1.1.0 updates * Add version command * Fix parsing of ELF where the program header does not directly follow the file header * Add install-ability via `cmake install` * Incorporate slightly modified Win32 libusb find helpers * Add ability to access device using stdio_usb * 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 * Add a sudo suggestion when can't connect on linux/macos --- CMakeLists.txt | 31 +- README.md | 81 ++-- cmake/FindLIBUSB.cmake | 6 +- main.cpp | 518 ++++++++++++++++------ picoboot_connection/picoboot_connection.c | 3 + picoboot_connection/picoboot_connection.h | 2 + 6 files changed, 476 insertions(+), 165 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 40a1dc1..b6c591a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,10 @@ cmake_minimum_required(VERSION 3.12) 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}')") @@ -13,6 +17,11 @@ get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMA if (NOT EXISTS ${PICO_SDK_PATH}) message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") endif () +include(${PICO_SDK_PATH}/pico_sdk_version.cmake) + +if (PICO_SDK_VERSION_STRING VERSION_LESS "1.3.0") + message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.3.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") +endif() set(CMAKE_CXX_STANDARD 14) @@ -27,9 +36,29 @@ else() add_subdirectory(${PICO_SDK_PATH}/src/common/pico_binary_info pico_binary_info) add_subdirectory(${PICO_SDK_PATH}/src/common/boot_uf2 boot_uf2_headers) add_subdirectory(${PICO_SDK_PATH}/src/common/boot_picoboot boot_picoboot_headers) + add_subdirectory(${PICO_SDK_PATH}/src/common/pico_usb_reset_interface pico_usb_reset_interface) add_subdirectory(${PICO_SDK_PATH}/src/host/pico_platform pico_platform) add_executable(picotool main.cpp) + set(PICOTOOL_VERSION 1.1.0) + set(SYSTEM_VERSION "${CMAKE_SYSTEM_NAME} ${CMAKE_SYSTEM_VERSION}") + 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}) - target_link_libraries(picotool pico_binary_info boot_uf2_headers boot_picoboot_headers pico_platform_headers picoboot_connection_cxx ${LIBUSB_LIBRARIES}) + # 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 + pico_usb_reset_interface_headers + picoboot_connection_cxx + ${LIBUSB_LIBRARIES}) + # allow `make install` + install(TARGETS picotool) endif() diff --git a/README.md b/README.md index beb551a..d193f7c 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,9 @@ nmake ``` ## Overview -Picotool is a tool for inspecting RP2040 binaries, and interacting with RP2040 devices when they are in BOOTSEL mode. +`picotool` is a tool for inspecting RP2040 binaries, and interacting with RP2040 devices when they are in BOOTSEL mode. (As of version 1.1 of `picotool` it is also possible to interact with RP2040 devices that are not in BOOTSEL mode, but are using USB stdio support from the Raspberry Pi Pico SDK by using the `-f` argument of `picotool`). -Note for full documentation see https://rptl.io/pico-get-started +Note for additional documentation see https://rptl.io/pico-get-started ```text $ picotool help @@ -41,25 +41,26 @@ PICOTOOL: Tool for interacting with a RP2040 device in BOOTSEL mode, or with a RP2040 binary SYNOPSYS: - picotool info [-b] [-p] [-d] [-l] [-a] [--bus ] [--address ] + picotool info [-b] [-p] [-d] [-l] [-a] [--bus ] [--address ] [-f] [-F] picotool info [-b] [-p] [-d] [-l] [-a] [-t ] - picotool load [-v] [-r] [-t ] [--bus ] [--address ] - picotool save [-p] [--bus ] [--address ] [-t ] - picotool save -a [--bus ] [--address ] [-t ] - picotool save -r [--bus ] [--address ] [-t ] - picotool verify [--bus ] [--address ] [-t ] [-r ] - picotool reboot [-a] [-u] [--bus ] [--address ] + picotool load [-n] [-N] [-v] [-x] [-t ] [-o ] [--bus ] [--address ] [-f] [-F] + picotool save [-p] [--bus ] [--address ] [-f] [-F] [-t ] + picotool save -a [--bus ] [--address ] [-f] [-F] [-t ] + picotool save -r [--bus ] [--address ] [-f] [-F] [-t ] + picotool verify [--bus ] [--address ] [-f] [-F] [-t ] [-r ] [-o ] + picotool reboot [-a] [-u] [--bus ] [--address ] [-f] [-F] + picotool version [-s] picotool help [] 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 " for more info ``` @@ -74,15 +75,14 @@ can find (See Binary Info section below). The info command is for reading this i 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 ] [--address ] + picotool info [-b] [-p] [-d] [-l] [-a] [--bus ] [--address ] [-f] [-F] picotool info [-b] [-p] [-d] [-l] [-a] [-t ] OPTIONS: @@ -104,13 +104,23 @@ TARGET SELECTION: Filter devices by USB bus number --address Filter devices by USB device address + -f, --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 + -F, --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 To target a file The file name -t Specify file type (uf2 | elf | bin) explicitly, ignoring file extension + ``` +Note the -f arguments vary slightly for Windows vs macOS / Unix platforms. + e.g. ```text @@ -180,9 +190,9 @@ SAVE: Save the program / memory stored in flash on the device to a file. SYNOPSYS: - picotool save [-p] [--bus ] [--address ] [-t ] - picotool save -a [--bus ] [--address ] [-t ] - picotool save -r [--bus ] [--address ] [-t ] + picotool save [-p] [--bus ] [--address ] [-f] [-F] [-t ] + picotool save -a [--bus ] [--address ] [-f] [-F] [-t ] + picotool save -r [--bus ] [--address ] [-f] [-F] [-t ] OPTIONS: Selection of data to save @@ -191,7 +201,8 @@ OPTIONS: -a, --all Save all of flash memory -r, --range - Save a range of memory; note that the range is expanded to 256 byte boundaries + Save a range of memory. Note that UF2s always store complete 256 byte-aligned blocks of 256 bytes, and the range is + expanded accordingly The lower address bound in hex @@ -201,6 +212,13 @@ OPTIONS: Filter devices by USB bus number --address Filter devices by USB device address + -f, --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 + -F, --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 File to save to The file name @@ -252,11 +270,7 @@ Basic information includes - 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 @@ -394,11 +408,6 @@ quotes, newlines etc you may have better luck defining via bi_decl in the code. ## 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 @@ -438,7 +447,7 @@ enum { ### 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/cmake/FindLIBUSB.cmake b/cmake/FindLIBUSB.cmake index c8cc62c..26e924c 100644 --- a/cmake/FindLIBUSB.cmake +++ b/cmake/FindLIBUSB.cmake @@ -19,10 +19,12 @@ else (LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES) pkg_check_modules(PC_LIBUSB libusb-1.0) ENDIF(NOT WIN32) FIND_PATH(LIBUSB_INCLUDE_DIR libusb.h + HINTS $ENV{LIBUSB_ROOT}/include/libusb-1.0 PATHS ${PC_LIBUSB_INCLUDEDIR} ${PC_LIBUSB_INCLUDE_DIRS}) - FIND_LIBRARY(LIBUSB_LIBRARIES NAMES usb-1.0 + FIND_LIBRARY(LIBUSB_LIBRARIES NAMES libusb-1.0 usb-1.0 usb + HINTS $ENV{LIBUSB_ROOT}/VS2019/MS32/static PATHS ${PC_LIBUSB_LIBDIR} ${PC_LIBUSB_LIBRARY_DIRS}) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(LIBUSB DEFAULT_MSG LIBUSB_LIBRARIES LIBUSB_INCLUDE_DIR) MARK_AS_ADVANCED(LIBUSB_INCLUDE_DIR LIBUSB_LIBRARIES) -endif (LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES) \ No newline at end of file +endif (LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES) diff --git a/main.cpp b/main.cpp index fae9203..0f4c5b1 100644 --- a/main.cpp +++ b/main.cpp @@ -10,7 +10,7 @@ #include "cli.h" #include "clipp/clipp.h" -#include +#include #include #include #include @@ -24,11 +24,18 @@ #include #include #include +#include + #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 +#endif + // tsk namespace is polluted on windows #ifdef _MSC_VER #undef min @@ -59,7 +66,6 @@ using std::map; typedef map>> device_map; typedef unsigned int uint; -static libusb_context *ctx; auto memory_names = map{ {memory_type::sram, "RAM"}, @@ -80,7 +86,7 @@ static string hex_string(int value, int width=8, bool prefix=true) { std::array, 10> pin_functions{{ {"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""}, - {"SPI0 RX", "SPI0 CSn", "SPI0 SCK", "SPI0 TX", "SPI0 RX", "SPI0 CSn", "SPI0 SCK", "SPI0 TX", "SPI1 RX", "SPI1 CSn", "SPI1 SCK", "SPI1 TX", "SPI1 RX", "SPI1 CSn", "SPI1 SCK", "PI1 TX", "SPI0 RX", "SPI0 CSn", "SPI0 SCK", "SPI0 TX", "SPI0 RX", "SPI0 CSn", "SPI0 SCK", "SPI0 TX", "SPI1 RX", "SPI1 CSn", "SPI1 SCK", "SPI1 TX", "SPI1 RX", "SPI1 CSn"}, + {"SPI0 RX", "SPI0 CSn", "SPI0 SCK", "SPI0 TX", "SPI0 RX", "SPI0 CSn", "SPI0 SCK", "SPI0 TX", "SPI1 RX", "SPI1 CSn", "SPI1 SCK", "SPI1 TX", "SPI1 RX", "SPI1 CSn", "SPI1 SCK", "SPI1 TX", "SPI0 RX", "SPI0 CSn", "SPI0 SCK", "SPI0 TX", "SPI0 RX", "SPI0 CSn", "SPI0 SCK", "SPI0 TX", "SPI1 RX", "SPI1 CSn", "SPI1 SCK", "SPI1 TX", "SPI1 RX", "SPI1 CSn"}, {"UART0 TX", "UART0 RX", "UART0 CTS", "UART0 RTS", "UART1 TX", "UART1 RX", "UART1 CTS", "UART1 RTS", "UART1 TX", "UART1 RX", "UART1 CTS", "UART1 RTS", "UART0 TX", "UART0 RX", "UART0 CTS", "UART0 RTS", "UART0 TX", "UART0 RX", "UART0 CTS", "UART0 RTS", "UART1 TX", "UART1 RX", "UART1 CTS", "UART1 RTS", "UART1 TX", "UART1 RX", "UART1 CTS", "UART1 RTS", "UART0 TX", "UART0 RX"}, {"I2C0 SDA", "I2C0 SCL", "I2C1 SDA", "I2C1 SCL", "I2C0 SDA", "I2C0 SCL", "I2C1 SDA", "I2C1 SCL", "I2C0 SDA", "I2C0 SCL", "I2C1 SDA", "I2C1 SCL", "I2C0 SDA", "I2C0 SCL", "I2C1 SDA", "I2C1 SCL", "I2C0 SDA", "I2C0 SCL", "I2C1 SDA", "I2C1 SCL", "I2C0 SDA", "I2C0 SCL", "I2C1 SDA", "I2C1 SCL", "I2C0 SDA", "I2C0 SCL", "I2C1 SDA", "I2C1 SCL", "I2C0 SDA", "I2C0 SCL"}, {"PWM0 A", "PWM0 B", "PWM1 A", "PWM1 B", "PWM2 A", "PWM2 B", "PWM3 A", "PWM3 B", "PWM4 A", "PWM4 B", "PWM5 A", "PWM5 B", "PWM6 A", "PWM6 B", "PWM7 A", "PWM7 B", "PWM0 A", "PWM0 B", "PWM1 A", "PWM1 B", "PWM2 A", "PWM2 B", "PWM3 A", "PWM3 B", "PWM4 A", "PWM4 B", "PWM5 A", "PWM5 B", "PWM6 A", "PWM6 B"}, @@ -140,8 +146,25 @@ struct range { 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 struct range_map { struct mapping { @@ -150,19 +173,20 @@ template struct range_map { 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.upper_bound(r.from); // first element that starts after r.from + if (f != m.begin()) f--; // back up, to catch element that starts on or before r.from + for(; f != m.end() && f->first < r.to; f++) { // loop till we can't possibly overlap + range r2(f->first, f->second.first); + if (r2.intersects(r)) { + fail(ERROR_FORMAT, "Found overlapping memory ranges 0x%08x->0x%08x and 0x%08x->%08x\n", + r.from, r.to, r2.from, r2.to); + } + } m.insert(std::make_pair(r.from, std::make_pair(r.to, t))); } } @@ -186,7 +210,7 @@ template struct range_map { uint32_t next(uint32_t p) { auto f = m.upper_bound(p); if (f == m.end()) { - std::numeric_limits::max(); + return std::numeric_limits::max(); } return f->first; } @@ -213,11 +237,13 @@ using cli::value; 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 +261,9 @@ struct _settings { 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,11 +276,17 @@ struct _settings { struct { bool verify = false; bool execute = false; + bool no_overwrite = false; + bool no_overwrite_force = false; } load; struct { bool all = false; } save; + + struct { + bool semantic = false; + } version; }; _settings settings; std::shared_ptr selected_cmd; @@ -263,6 +297,10 @@ auto device_selection = .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)) @@ -278,7 +316,7 @@ auto file_selection = 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; @@ -309,7 +347,7 @@ struct info_command : public cmd { 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 ( @@ -319,7 +357,7 @@ struct verify_command : public cmd { (option('r', "--range").set(settings.range_set) % "Compare a sub range of memory only" & hex("from").set(settings.from) % "The lower address bound in hex" & hex("to").set(settings.to) % "The upper address bound in hex").force_expand_help(true) + - (option('o', "--offset").set(settings.offset_set) % "Specify a load address for a BIN file" & + (option('o', "--offset").set(settings.offset_set) % "Specify the load address when comparing with a BIN file" & hex("offset").set(settings.offset) % "Load offset (memory address; default 0x10000000)").force_expand_help(true) ).min(0).doc_non_optional(true) % "Address options" ); @@ -332,7 +370,7 @@ struct verify_command : public cmd { 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 ( @@ -358,17 +396,19 @@ struct save_command : public cmd { 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" + file_selection % "File to load from" + ( - option('o', "--offset").set(settings.offset_set) % "Specify a load address for a BIN file" & + option('o', "--offset").set(settings.offset_set) % "Specify the load address for a BIN file" & hex("offset").set(settings.offset) % "Load offset (memory address; default 0x10000000)" ).force_expand_help(true) % "BIN file options" + device_selection % "Target device selection" @@ -382,7 +422,7 @@ struct load_command : public cmd { 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; @@ -399,19 +439,53 @@ struct help_command : public cmd { } } help_cmd; +struct version_command : public cmd { + version_command() : cmd("version") {} + 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"; + return false; + } + + device_support get_device_support() override { + return device_support::none; + } + + group get_cli() override { + return group( + option('s', "--semantic").set(settings.version.semantic) % "Output semantic version number only" + ); + } + + string get_doc() const override { + return "Display picotool version"; + } +} 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"; } @@ -423,6 +497,7 @@ vector> commands { std::shared_ptr(new save_command()), std::shared_ptr(new verify_command()), std::shared_ptr(new reboot_command()), + std::shared_ptr(new version_command()), std::shared_ptr(new help_command()), }; @@ -444,6 +519,13 @@ std::basic_string uppercase(const std::basic_string& s) clipp::formatting_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) { @@ -580,7 +662,8 @@ int parse(const int argc, char **argv) { 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; } @@ -666,10 +749,6 @@ struct memory_access { } }; -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 @@ -862,16 +941,6 @@ static void read_and_check_elf32_header(FILE *in, elf32_header& eh_out) { } } -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"); } @@ -934,6 +1003,12 @@ string read_string(memory_access &access, uint32_t addr) { } 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); @@ -1181,6 +1256,9 @@ void build_rmap_elf(FILE *file, range_map& rmap) { } if (eh.ph_num) { vector entries(eh.ph_num); + if (fseek(file, eh.ph_offset, SEEK_SET)) { + return fail_read_error(); + } if (eh.ph_num != fread(&entries[0], sizeof(struct elf32_ph_entry), eh.ph_num, file)) { fail_read_error(); } @@ -1332,9 +1410,7 @@ void info_guts(memory_access &raw_access) { 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) { @@ -1380,9 +1456,7 @@ void info_guts(memory_access &raw_access) { } }); - 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); @@ -1489,62 +1563,70 @@ void info_guts(memory_access &raw_access) { } } -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); } @@ -1574,8 +1656,8 @@ struct progress_bar { 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; @@ -1603,9 +1685,7 @@ void save_command::execute(device_map &devices) { 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, @@ -1690,6 +1770,7 @@ void save_command::execute(device_map &devices) { throw; } } + return false; } vector get_colaesced_ranges(file_memory_access &file_access) { @@ -1712,13 +1793,32 @@ vector get_colaesced_ranges(file_memory_access &file_access) { 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); @@ -1727,6 +1827,16 @@ void load_command::execute(device_map &devices) { 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); @@ -1801,15 +1911,18 @@ void load_command::execute(device_map &devices) { 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) { @@ -1906,36 +2019,94 @@ void verify_command::execute(device_map &devices) { } } } + 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"; +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 { - picoboot_memory_access raw_access(con); - uint program_base = SRAM_START; - std::vector 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')) - }; + // 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 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; + 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"; } - throw e; } - fail(ERROR_NOT_POSSIBLE, "Reboot into USB boot not implemented yet"); } + return true; } #if defined(_WIN32) @@ -1965,6 +2136,8 @@ void cancelled(int) { } int main(int argc, char **argv) { + libusb_context *ctx = nullptr; + int tw=0, th=0; get_terminal_size(tw, th); if (tw) { @@ -1977,6 +2150,9 @@ int main(int argc, char **argv) { return 0; } + // save complicating the grammar + if (settings.force_no_reboot) settings.force = true; + struct libusb_device **devs = nullptr; device_map devices; vector to_close; @@ -1984,58 +2160,148 @@ int main(int argc, char **argv) { 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); + } + + // 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"); } - if (result != dr_error) { - devices[result].push_back(std::make_pair(*dev, handle)); + 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()) { + 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."); +#if defined(__linux__) || defined(__APPLE__) + printer(dr_vidpid_bootrom_cant_connect, + " appears to be a RP2040 device in BOOTSEL mode, but picotool was unable to connect. Maybe try 'sudo' or check your permissions."); +#else + printer(dr_vidpid_bootrom_cant_connect, + " appears to be a RP2040 device in BOOTSEL mode, but picotool was unable to connect. You may need to install a driver. See \"Getting started with Raspberry Pi Pico\" for more information"); +#endif + 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 @@ static bool verbose; #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 @@ enum picoboot_device_result picoboot_open_device(libusb_device *device, libusb_d 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 #include #include "boot/picoboot.h" @@ -24,6 +25,7 @@ enum picoboot_device_result { 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);