diff --git a/README.md b/README.md index 572586c..626bad7 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,8 @@ TARGET SELECTION: Filter by product id --ser Filter by serial number + --rp2040 + Use RP2040-specific workarounds when using a custom vid/pid on Windows (ignored on other platforms) -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 @@ -221,6 +223,8 @@ TARGET SELECTION: Filter by product id --ser Filter by serial number + --rp2040 + Use RP2040-specific workarounds when using a custom vid/pid on Windows (ignored on other platforms) -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 @@ -322,6 +326,8 @@ OPTIONS: Filter by product id --ser Filter by serial number + --rp2040 + Use RP2040-specific workarounds when using a custom vid/pid on Windows (ignored on other platforms) -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 @@ -388,6 +394,8 @@ OPTIONS: Filter by product id --ser Filter by serial number + --rp2040 + Use RP2040-specific workarounds when using a custom vid/pid on Windows (ignored on other platforms) -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 @@ -462,6 +470,8 @@ OPTIONS: Filter by product id --ser Filter by serial number + --rp2040 + Use RP2040-specific workarounds when using a custom vid/pid on Windows (ignored on other platforms) -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 @@ -511,6 +521,8 @@ OPTIONS: Filter by product id --ser Filter by serial number + --rp2040 + Use RP2040-specific workarounds when using a custom vid/pid on Windows (ignored on other platforms) -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 @@ -724,6 +736,8 @@ OPTIONS: Filter by product id --ser Filter by serial number + --rp2040 + Use RP2040-specific workarounds when using a custom vid/pid on Windows (ignored on other platforms) -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 @@ -881,6 +895,8 @@ OPTIONS: Filter by product id --ser Filter by serial number + --rp2040 + Use RP2040-specific workarounds when using a custom vid/pid on Windows (ignored on other platforms) -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 @@ -980,6 +996,8 @@ TARGET SELECTION: Filter by product id --ser Filter by serial number + --rp2040 + Use RP2040-specific workarounds when using a custom vid/pid on Windows (ignored on other platforms) -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 @@ -1040,6 +1058,8 @@ TARGET SELECTION: Filter by product id --ser Filter by serial number + --rp2040 + Use RP2040-specific workarounds when using a custom vid/pid on Windows (ignored on other platforms) -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 @@ -1087,6 +1107,8 @@ OPTIONS: Filter by product id --ser Filter by serial number + --rp2040 + Use RP2040-specific workarounds when using a custom vid/pid on Windows (ignored on other platforms) -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 @@ -1131,6 +1153,8 @@ OPTIONS: Filter by product id --ser Filter by serial number + --rp2040 + Use RP2040-specific workarounds when using a custom vid/pid on Windows (ignored on other platforms) -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 @@ -1216,6 +1240,8 @@ OPTIONS: Filter by product id --ser Filter by serial number + --rp2040 + Use RP2040-specific workarounds when using a custom vid/pid on Windows (ignored on other platforms) -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 @@ -1281,6 +1307,8 @@ TARGET SELECTION: Filter by product id --ser Filter by serial number + --rp2040 + Use RP2040-specific workarounds when using a custom vid/pid on Windows (ignored on other platforms) -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 @@ -1630,3 +1658,9 @@ this requirement (see the [hello_usb](https://github.com/raspberrypi/pico-exampl 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). + +### Zadig + +To communicate with RP2040 in BOOTSEL mode on Windows, you will need to install a driver. To do this, download and run [Zadig](http://zadig.akeo.ie), select `RP2 Boot (Interface 1)` from the dropdown box and select `WinUSB` as the driver, and click on the "Install Driver" button. Wait for the installation to complete - this may take a few minutes. + +This is only required for RP2040. diff --git a/main.cpp b/main.cpp index 063d3c6..6806ade 100644 --- a/main.cpp +++ b/main.cpp @@ -481,6 +481,7 @@ struct _settings { int vid=-1; int pid=-1; string ser; + bool force_rp2040 = false; uint32_t offset = 0; uint32_t from = 0; uint32_t to = 0; @@ -610,6 +611,7 @@ auto device_selection = (option("--vid") & integer("vid").set(settings.vid).if_missing([] { return "missing vid"; })) % "Filter by vendor id" + (option("--pid") & integer("pid").set(settings.pid)) % "Filter by product id" + (option("--ser") & value("ser").set(settings.ser)) % "Filter by serial number" + + option("--rp2040").set(settings.force_rp2040) % "Use RP2040-specific workarounds when using a custom vid/pid on Windows (ignored on other platforms)" + 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 USB drive mounted" ).min(0).doc_non_optional(true).collapse_synopsys("device-selection"); @@ -8703,6 +8705,13 @@ int main(int argc, char **argv) { if (result != dr_error) { devices[result].emplace_back(std::make_tuple(chip, *dev, handle)); } + + if (settings.vid == 0 && !settings.ser.empty() && !devices[dr_vidpid_bootrom_ok].empty()) { + // Searching with no vid/pid filtering (ie opening all devices) can cause issues, so stop + // searching when we have a serial number, as we know we have the correct device + DEBUG_LOG("Found bootrom device with serial number, so stopping search"); + break; + } } } auto supported = selected_cmd->get_device_support(); @@ -8720,6 +8729,11 @@ int main(int argc, char **argv) { bool had_note = false; fos << missing_device_string(tries>0, selected_cmd->requires_rp2350()); if (tries) { +#if defined(_WIN32) + if (settings.force_rp2040) { + fos << " You may need to install a driver via Zadig. See Zadig in the README (https://github.com/raspberrypi/picotool#zadig) for more information."; + } +#endif 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 } @@ -8742,7 +8756,7 @@ int main(int argc, char **argv) { " appears to have a USB serial connection, but picotool was unable to connect. Maybe try 'sudo' or check your permissions."); #else printer(dr_vidpid_bootrom_cant_connect, - " appears to be in BOOTSEL mode, but picotool was unable to connect. You may need to install a driver via Zadig. See \"Getting started with Raspberry Pi Pico\" for more information"); + " appears to be in BOOTSEL mode, but picotool was unable to connect. You may need to install a driver via Zadig. See Zadig in the README (https://github.com/raspberrypi/picotool#zadig) for more information"); printer(dr_vidpid_stdio_usb_cant_connect, " appears to have a USB serial connection, but picotool was unable to connect."); #endif @@ -8751,13 +8765,8 @@ int main(int argc, char **argv) { printer(dr_vidpid_micropython, " appears to be an RP-series MicroPython device not in BOOTSEL mode."); if (selected_cmd->force_requires_pre_reboot()) { - #if defined(_WIN32) - printer(dr_vidpid_stdio_usb, - " appears to have 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 have 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, @@ -8797,13 +8806,14 @@ int main(int argc, char **argv) { // we reboot into BOOTSEL mode and disable MSC interface (the 1 here) auto &to_reboot = std::get<1>(devices[dr_vidpid_stdio_usb][0]); auto &to_reboot_handle = std::get<2>(devices[dr_vidpid_stdio_usb][0]); + unsigned int disable_mask = 1; // disable MSC interface #if defined(_WIN32) { struct libusb_device_descriptor desc; libusb_get_device_descriptor(to_reboot, &desc); - if (desc.idProduct == PRODUCT_ID_RP2040_STDIO_USB) { - fail(ERROR_NOT_POSSIBLE, - "Forced commands do not work with RP2040 on Windows - you can force reboot into BOOTSEL mode via 'picotool reboot -f -u' instead."); + if (desc.idProduct == PRODUCT_ID_RP2040_STDIO_USB || settings.force_rp2040) { + disable_mask = 0; // enable MSC interface so Zadig works correctly + settings.force_rp2040 = true; } } #endif @@ -8820,7 +8830,7 @@ int main(int argc, char **argv) { } } - reboot_device(to_reboot, to_reboot_handle, true, 1); + reboot_device(to_reboot, to_reboot_handle, true, disable_mask); fos << "The device was asked to reboot into BOOTSEL mode so the command can be executed."; } else if (tries == 1) { fos << "\nWaiting for device to reboot"; @@ -8841,6 +8851,18 @@ int main(int argc, char **argv) { // again is to assume it has the same serial number. settings.address = -1; settings.bus = -1; + if (settings.pid != -1 || settings.vid != -1) { + // vid/pid filtering was enabled, but should change in BOOTSEL mode, so needs to be disabled + if (settings.ser.empty()) { + // this is an RP2040 running a no_flash binary, so will have a standard RP2040 vid/pid in BOOTSEL mode + settings.vid = -1; // -1 means filter for standard vid/pid + settings.pid = -1; + } else { + // skip vid/pid filtering, as it can be white-labelled on RP2350, and we know the serial number + settings.vid = 0; // 0 means skip vid/pid filtering entirely + settings.pid = -1; + } + } continue; } } diff --git a/picoboot_connection/picoboot_connection.c b/picoboot_connection/picoboot_connection.c index 9ee797e..8f3bf01 100644 --- a/picoboot_connection/picoboot_connection.c +++ b/picoboot_connection/picoboot_connection.c @@ -69,7 +69,7 @@ enum picoboot_device_result picoboot_open_device(libusb_device *device, libusb_d int ret = libusb_get_device_descriptor(device, &desc); enum picoboot_device_result res = dr_vidpid_unknown; if (ret && verbose) { - output("Failed to read device descriptor\n"); + output("Failed to read device descriptor %s\n", libusb_error_name(ret)); } if (!ret) { if (pid >= 0) { @@ -107,14 +107,14 @@ enum picoboot_device_result picoboot_open_device(libusb_device *device, libusb_d } ret = libusb_get_active_config_descriptor(device, &config); if (ret && verbose) { - output("Failed to read config descriptor\n"); + output("Failed to read config descriptor %s\n", libusb_error_name(ret)); } } if (!ret) { ret = libusb_open(device, dev_handle); if (ret && verbose) { - output("Failed to open device %d\n", ret); + output("Failed to open device %s\n", libusb_error_name(ret)); } if (ret) { if (vid == 0 || strlen(ser) != 0) { @@ -169,7 +169,7 @@ enum picoboot_device_result picoboot_open_device(libusb_device *device, libusb_d if (verbose) output("Found PICOBOOT interface\n"); ret = libusb_claim_interface(*dev_handle, interface); if (ret) { - if (verbose) output("Failed to claim interface\n"); + if (verbose) output("Failed to claim interface %s\n", libusb_error_name(ret)); return dr_vidpid_bootrom_no_interface; } } else { @@ -180,16 +180,22 @@ enum picoboot_device_result picoboot_open_device(libusb_device *device, libusb_d if (!ret) { if (*chip == unknown) { - struct picoboot_get_info_cmd info_cmd; - info_cmd.bType = PICOBOOT_GET_INFO_SYS, - info_cmd.dParams[0] = (uint32_t) (SYS_INFO_CHIP_INFO); - uint32_t word_buf[64]; - // RP2040 doesn't have this function, so returns non-zero - int info_ret = picoboot_get_info(*dev_handle, &info_cmd, (uint8_t*)word_buf, sizeof(word_buf)); - if (info_ret) { + if (desc.idVendor == VENDOR_ID_RASPBERRY_PI && desc.idProduct == PRODUCT_ID_RP2040_USBBOOT) { + // Set model based on bootrom vid/pid for RP2040, as it cannot be white-labelled *chip = rp2040; } else { - *chip = rp2350; + // Otherwise check the chip info command exists + struct picoboot_get_info_cmd info_cmd; + info_cmd.bType = PICOBOOT_GET_INFO_SYS, + info_cmd.dParams[0] = (uint32_t) (SYS_INFO_CHIP_INFO); + uint32_t word_buf[64]; + // Other devices don't have this function, so will return errors + int info_ret = picoboot_get_info(*dev_handle, &info_cmd, (uint8_t*)word_buf, sizeof(word_buf)); + if (info_ret) { + return dr_vidpid_unknown; + } else { + *chip = rp2350; + } } } if (strlen(ser) != 0) { @@ -301,7 +307,7 @@ int picoboot_cmd(libusb_device_handle *usb_device, struct picoboot_cmd *cmd, uin ret = libusb_bulk_transfer(usb_device, out_ep, (uint8_t *) cmd, sizeof(struct picoboot_cmd), &sent, 3000); if (ret != 0 || sent != sizeof(struct picoboot_cmd)) { - output(" ...failed to send command %d\n", ret); + output(" ...failed to send command %s\n", libusb_error_name(ret)); return ret; } @@ -321,7 +327,7 @@ int picoboot_cmd(libusb_device_handle *usb_device, struct picoboot_cmd *cmd, uin int received = 0; ret = libusb_bulk_transfer(usb_device, in_ep, buffer, cmd->dTransferLength, &received, timeout); if (ret != 0 || received != (int) cmd->dTransferLength) { - output(" ...failed to receive data %d %d/%d\n", ret, received, cmd->dTransferLength); + output(" ...failed to receive data %s %d/%d\n", libusb_error_name(ret), received, cmd->dTransferLength); if (!ret) ret = 1; return ret; } @@ -329,7 +335,7 @@ int picoboot_cmd(libusb_device_handle *usb_device, struct picoboot_cmd *cmd, uin if (verbose) output(" send %d...\n", cmd->dTransferLength); ret = libusb_bulk_transfer(usb_device, out_ep, buffer, cmd->dTransferLength, &sent, timeout); if (ret != 0 || sent != (int) cmd->dTransferLength) { - output(" ...failed to send data %d %d/%d\n", ret, sent, cmd->dTransferLength); + output(" ...failed to send data %s %d/%d\n", libusb_error_name(ret), sent, cmd->dTransferLength); if (!ret) ret = 1; picoboot_cmd_status_verbose(usb_device, NULL, true); return ret;