Skip to content

Commit

Permalink
Add CLI option to enable detection of devices with reset interface
Browse files Browse the repository at this point in the history
  • Loading branch information
tomas-pecserke committed Jun 5, 2023
1 parent b203309 commit a25aa9a
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 20 deletions.
44 changes: 32 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,14 @@ PICOTOOL:
Tool for interacting with a RP2040 device in BOOTSEL mode, or with a RP2040 binary
SYNOPSIS:
picotool info [-b] [-p] [-d] [-l] [-a] [--bus <bus>] [--address <addr>] [-f] [-F]
picotool info [-b] [-p] [-d] [-l] [-a] [--bus <bus>] [--address <addr>] [-i] [-f] [-F]
picotool info [-b] [-p] [-d] [-l] [-a] <filename> [-t <type>]
picotool load [-n] [-N] [-u] [-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 load [-n] [-N] [-u] [-v] [-x] <filename> [-t <type>] [-o <offset>] [--bus <bus>] [--address <addr>] [-i] [-f] [-F]
picotool save [-p] [--bus <bus>] [--address <addr>] [-i] [-f] [-F] <filename> [-t <type>]
picotool save -a [--bus <bus>] [--address <addr>] [-i] [-f] [-F] <filename> [-t <type>]
picotool save -r <from> <to> [--bus <bus>] [--address <addr>] [-i] [-f] [-F] <filename> [-t <type>]
picotool verify [--bus <bus>] [--address <addr>] [-i] [-f] [-F] <filename> [-t <type>] [-r <from> <to>] [-o <offset>]
picotool reboot [-a] [-u] [--bus <bus>] [--address <addr>] [-i] [-f] [-F]
picotool version [-s]
picotool help [<cmd>]
Expand Down Expand Up @@ -111,7 +111,7 @@ INFO:
Without any arguments, this will display basic information for all connected RP2040 devices in BOOTSEL mode
SYNOPSIS:
picotool info [-b] [-p] [-d] [-l] [-a] [--bus <bus>] [--address <addr>] [-f] [-F]
picotool info [-b] [-p] [-d] [-l] [-a] [--bus <bus>] [--address <addr>] [-i] [-f] [-F]
picotool info [-b] [-p] [-d] [-l] [-a] <filename> [-t <type>]
OPTIONS:
Expand All @@ -133,6 +133,8 @@ TARGET SELECTION:
Filter devices by USB bus number
--address <addr>
Filter devices by USB device address
-i, --enable-reset-interface
Enable detection of stdio_usb-compatible reset interface for custom USB devices
-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
Expand Down Expand Up @@ -218,7 +220,7 @@ LOAD:
Load the program / memory range stored in a file onto the device.
SYNOPSIS:
picotool load [-n] [-N] [-u] [-v] [-x] <filename> [-t <type>] [-o <offset>] [--bus <bus>] [--address <addr>] [-f] [-F]
picotool load [-n] [-N] [-u] [-v] [-x] <filename> [-t <type>] [-o <offset>] [--bus <bus>] [--address <addr>] [-i] [-f] [-F]
OPTIONS:
Post load actions
Expand Down Expand Up @@ -249,6 +251,8 @@ OPTIONS:
Filter devices by USB bus number
--address <addr>
Filter devices by USB device address
-i, --enable-reset-interface
Enable detection of stdio_usb-compatible reset interface for custom USB devices
-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
Expand All @@ -275,9 +279,9 @@ SAVE:
Save the program / memory stored in flash on the device to a file.
SYNOPSIS:
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 save [-p] [--bus <bus>] [--address <addr>] [-i] [-f] [-F] <filename> [-t <type>]
picotool save -a [--bus <bus>] [--address <addr>] [-i] [-f] [-F] <filename> [-t <type>]
picotool save -r <from> <to> [--bus <bus>] [--address <addr>] [-i] [-f] [-F] <filename> [-t <type>]
OPTIONS:
Selection of data to save
Expand All @@ -297,6 +301,8 @@ OPTIONS:
Filter devices by USB bus number
--address <addr>
Filter devices by USB device address
-i, --enable-reset-interface
Enable detection of stdio_usb-compatible reset interface for custom USB devices
-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
Expand Down Expand Up @@ -540,3 +546,17 @@ in binary.
If you ctrl+c out of the middle of a long operation, then libusb seems to get a bit confused, which means we aren't able
to unlock our lockout of USB MSD writes (we have turned them off so the user doesn't step on their own toes). Simply running
`picotool info` again will unlock it properly the next time (or you can reboot the device).

### Support for custom stdio_usb compatible reset interface

When you include [`pico_stdio_usb`](https://www.raspberrypi.com/documentation/pico-sdk/runtime.html#pico_stdio_usb)
module and enable default Pico SDK USB stack using
[`stdio_usb_init()`](https://www.raspberrypi.com/documentation/pico-sdk/runtime.html#gab87bcfa3f24e5a3fe92a944f9eecc460),
you also automatically include a custom [USB vendor interface](https://www.usb.org/defined-class-codes).
This interface implements commands to restart the device either to bootloader or to the application.

However `picotool` does not recognize the device as standard RP2040 device,
when it uses custom USB setup via [TinyUSB](https://docs.tinyusb.org/).
Custom devices implementing stdio_usb compatible
[reset interface](https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/pico_stdio_usb/reset_interface.c)
are supported using picotool `--detect-reset-interface` or `-i` CLI option.
29 changes: 22 additions & 7 deletions main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ struct _settings {
bool reboot_app_specified = false;
bool force = false;
bool force_no_reboot = false;
bool detect_reset_interface = false;

struct {
bool show_basic = false;
Expand Down Expand Up @@ -297,7 +298,10 @@ auto device_selection =
(option("--bus") & integer("bus").min_value(0).max_value(255).set(settings.bus)
.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_missing([] { return "missing address"; })) % "Filter devices by USB device address" +
option('i', "--detect-reset-interface").set(settings.detect_reset_interface)
% "Enable detection of devices with a custom USB vendor interface compatible with stdio_usb reset interface."
"The stdio_usb reset interface is implemented by Pico SDK in default USB serial setup."
#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"
Expand Down Expand Up @@ -2110,7 +2114,10 @@ static int reboot_device(libusb_device *device, bool bootsel, uint disable_mask=

bool reboot_command::execute(device_map &devices) {
if (settings.force) {
reboot_device(devices[dr_vidpid_stdio_usb][0].first, settings.reboot_usb);
auto &to_reboot = devices[dr_vidpid_stdio_usb].empty()
? devices[dr_vidpid_stdio_usb][0].first
: devices[dr_reset_interface][0].first;
reboot_device(to_reboot, settings.reboot_usb);
if (!quiet) {
if (settings.reboot_usb) {
std::cout << "The device was asked to reboot into BOOTSEL mode.\n";
Expand Down Expand Up @@ -2230,7 +2237,7 @@ int main(int argc, char **argv) {
if (handle) {
to_close.push_back(handle);
}
if (result != dr_error) {
if (result != dr_error && (result != dr_reset_interface || settings.detect_reset_interface)) {
devices[result].push_back(std::make_pair(*dev, handle));
}
}
Expand All @@ -2242,7 +2249,7 @@ int main(int argc, char **argv) {
// fall thru
case cmd::device_support::one:
if (devices[dr_vidpid_bootrom_ok].empty() &&
(!settings.force || devices[dr_vidpid_stdio_usb].empty())) {
(!settings.force || (devices[dr_vidpid_stdio_usb].empty() && devices[dr_reset_interface].empty()))) {
bool had_note = false;
fos << missing_device_string(tries>0);
if (tries > 0) {
Expand Down Expand Up @@ -2276,19 +2283,25 @@ int main(int argc, char **argv) {
#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.");
printer(dr_reset_interface,
" appears to be a RP2040 device with a USB reset interface, 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.");
printer(dr_reset_interface,
" appears to be a RP2040 device with a USB reset interface, 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.");
printer(dr_reset_interface,
" appears to be a RP2040 device with a USB reset interface, 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)) {
(devices[dr_vidpid_bootrom_ok].empty() && (devices[dr_vidpid_stdio_usb].size() + devices[dr_reset_interface].size()) > 1)) {
fail(ERROR_NOT_POSSIBLE, "Command requires a single RP2040 device to be targeted.");
}
if (!devices[dr_vidpid_bootrom_ok].empty()) {
Expand All @@ -2306,13 +2319,15 @@ int main(int argc, char **argv) {
}
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) {
if ((devices[dr_vidpid_stdio_usb].size() + devices[dr_reset_interface].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;
auto &to_reboot = devices[dr_vidpid_stdio_usb].empty()
? devices[dr_vidpid_stdio_usb][0].first
: devices[dr_reset_interface][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) {
Expand Down
2 changes: 1 addition & 1 deletion picoboot_connection/picoboot_connection.c
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ enum picoboot_device_result picoboot_open_device(libusb_device *device, libusb_d
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) {
return dr_vidpid_stdio_usb;
return dr_reset_interface;
}
}

Expand Down
1 change: 1 addition & 0 deletions picoboot_connection/picoboot_connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ enum picoboot_device_result {
dr_vidpid_unknown,
dr_error,
dr_vidpid_stdio_usb,
dr_reset_interface,
};

enum picoboot_device_result picoboot_open_device(libusb_device *device, libusb_device_handle **dev_handle);
Expand Down

0 comments on commit a25aa9a

Please sign in to comment.