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);