Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add stdio_usb reset interface detection for custom VendorID / ProductID setups #83

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 51 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,9 @@ TARGET SELECTION:
Filter devices by USB bus number
--address <addr>
Filter devices by USB device address
-i, --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
-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 +221,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 +252,9 @@ OPTIONS:
Filter devices by USB bus number
--address <addr>
Filter devices by USB device address
-i, --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
-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 +281,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 +303,9 @@ OPTIONS:
Filter devices by USB bus number
--address <addr>
Filter devices by USB device address
-i, --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
-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 +549,33 @@ 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.

#### Permissions for devices with custom reset interface - Linux / macOS

To allow using `picotool` with your custom USB device without sudo,
add following UDEV rule to the end of `/etc/udev/rules.d/99-picotool.rules` file:

```udev
SUBSYSTEM=="usb", \
ATTRS{idVendor}=="<VID>", \
ATTRS{idProduct}=="<PID>", \
MODE="660", \
GROUP="plugdev"
```

Replace `<VID>` and `<PID>` with vendor and product identifier configured in your
custom USB stack respectively.
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)
tomas-pecserke marked this conversation as resolved.
Show resolved Hide resolved
% "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_reset_interface][0].first
: devices[dr_vidpid_stdio_usb][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 -i' 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_reset_interface][0].first
: 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) {
Expand Down
14 changes: 14 additions & 0 deletions picoboot_connection/picoboot_connection.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <pico/usb_reset_interface.h>

#include "picoboot_connection.h"

Expand Down Expand Up @@ -66,6 +67,19 @@ enum picoboot_device_result picoboot_open_device(libusb_device *device, libusb_d
}
if (!ret) {
if (desc.idVendor != VENDOR_ID_RASPBERRY_PI) {
ret = libusb_get_active_config_descriptor(device, &config);
if (ret && verbose) {
output("Failed to read config descriptor\n");
}

for (uint8_t 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) {
return dr_reset_interface;
}
}

return dr_vidpid_unknown;
}
switch (desc.idProduct) {
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