diff --git a/README.md b/README.md index e33e4ba..45ca5c3 100644 --- a/README.md +++ b/README.md @@ -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 ] [--address ] [-f] [-F] + picotool info [-b] [-p] [-d] [-l] [-a] [--bus ] [--address ] [--serial ] [-f] [-F] picotool info [-b] [-p] [-d] [-l] [-a] [-t ] - picotool load [-n] [-N] [-u] [-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 load [-n] [-N] [-u] [-v] [-x] [-t ] [-o ] [--bus ] [--address ] [--serial ] [-f] [-F] + picotool save [-p] [--bus ] [--address ] [--serial ] [-f] [-F] [-t ] + picotool save -a [--bus ] [--address ] [--serial ] [-f] [-F] [-t ] + picotool save -r [--bus ] [--address ] [--serial ] [-f] [-F] [-t ] + picotool verify [--bus ] [--address ] [--serial ] [-f] [-F] [-t ] [-r ] [-o ] + picotool reboot [-a] [-u] [--bus ] [--address ] [--serial ] [-f] [-F] picotool version [-s] picotool help [] @@ -133,6 +133,8 @@ TARGET SELECTION: Filter devices by USB bus number --address Filter devices by USB device address + --serial + Filter devices by serial id -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 @@ -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] [-t ] [-o ] [--bus ] [--address ] [-f] [-F] + picotool load [-n] [-N] [-u] [-v] [-x] [-t ] [-o ] [--bus ] [--address ] [--serial ] [-f] [-F] OPTIONS: Post load actions @@ -249,6 +251,8 @@ OPTIONS: Filter devices by USB bus number --address Filter devices by USB device address + --serial + Filter devices by serial id -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 @@ -275,9 +279,9 @@ SAVE: Save the program / memory stored in flash on the device to a file. SYNOPSIS: - 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 save [-p] [--bus ] [--address ] [--serial ] [-f] [-F] [-t ] + picotool save -a [--bus ] [--address ] [--serial ] [-f] [-F] [-t ] + picotool save -r [--bus ] [--address ] [--serial ] [-f] [-F] [-t ] OPTIONS: Selection of data to save @@ -297,6 +301,8 @@ OPTIONS: Filter devices by USB bus number --address Filter devices by USB device address + --serial + Filter devices by serial id -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 diff --git a/main.cpp b/main.cpp index 1f38c24..05c534e 100644 --- a/main.cpp +++ b/main.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include "boot/uf2.h" #include "picoboot_connection_cxx.h" @@ -255,6 +256,8 @@ struct _settings { uint32_t binary_start = FLASH_START; int bus=-1; int address=-1; + int port = -1; + string serial; uint32_t offset = 0; uint32_t from = 0; uint32_t to = 0; @@ -295,12 +298,25 @@ std::shared_ptr selected_cmd; 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 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" + ).min(0).doc_non_optional(true) + + ( + (option("--serial") & value("serial").set(settings.serial) + .if_missing([] { return "missing serial id"; })) + % "Filter devices by serial id" #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" + + 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); @@ -1582,28 +1598,25 @@ void info_guts(memory_access &raw_access) { } string missing_device_string(bool wasRetry) { - char b[256]; - if (wasRetry) { - strcpy(b, "Despite the reboot attempt, no "); - } else { - strcpy(b, "No "); + std::ostringstream oss; + oss << (wasRetry ? "Despite the reboot attempt, no" : "No") << " accessible RP2040 device"; + if (settings.bus != -1 && settings.serial.empty()) { + oss << 's'; + } + oss << " in BOOTSEL mode"; + if (!settings.serial.empty()) { + oss << " with serial ID '" << settings.serial << '\''; + } + oss << " was found"; + if (settings.bus != -1) { + oss << " at bus " << settings.bus; } - char *buf = b + strlen(b); - int buf_len = b + sizeof(b) - buf; if (settings.address != -1) { - if (settings.bus != -1) { - snprintf(buf, buf_len, "accessible RP2040 device in BOOTSEL mode was found at bus %d, address %d.", settings.bus, settings.address); - } else { - snprintf(buf, buf_len, "accessible RP2040 devices in BOOTSEL mode were found with address %d.", settings.address); - } - } else { - if (settings.bus != -1) { - snprintf(buf, buf_len, "accessible RP2040 devices in BOOTSEL mode were found found on bus %d.", settings.bus); - } else { - snprintf(buf, buf_len,"accessible RP2040 devices in BOOTSEL mode were found."); - } + oss << (settings.bus == -1 ? " with" : ",") << " address " << settings.address; } - return b; + oss << '.'; + + return oss.str(); } bool help_command::execute(device_map &devices) { @@ -2180,6 +2193,33 @@ void cancelled(int) { throw cancelled_exception(); } +string get_usb_device_serial(libusb_device *device, libusb_device_handle *handle) { + struct libusb_device_descriptor desc{}; + int ret = libusb_get_device_descriptor(device, &desc); + if (ret) { + return ""; + } + bool needs_closing = false; + if (handle == nullptr) { + ret = libusb_open(device, &handle); + if (ret) { + return ""; + } + needs_closing = true; + } + unsigned char data[33]; + string serial; + if (libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, data, 31)) { + data[32] = '\0'; + serial = reinterpret_cast(data); + } + if (needs_closing) { + libusb_close(handle); + } + + return serial; +} + int main(int argc, char **argv) { libusb_context *ctx = nullptr; @@ -2225,11 +2265,13 @@ int main(int argc, char **argv) { 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; + if (settings.port != -1 && settings.port != libusb_get_port_number(*dev)) continue; libusb_device_handle *handle = nullptr; auto result = picoboot_open_device(*dev, &handle); if (handle) { to_close.push_back(handle); } + if (!settings.serial.empty() && settings.serial != get_usb_device_serial(*dev, handle)) continue; if (result != dr_error) { devices[result].push_back(std::make_pair(*dev, handle)); } @@ -2311,8 +2353,14 @@ int main(int argc, char **argv) { "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; + // adjust filter settings, so we can find the device after reboot + if (!settings.serial.empty()) { + settings.serial = ""; + settings.bus = libusb_get_bus_number(to_reboot); + settings.port = libusb_get_port_number(to_reboot); + } + // we reboot into BOOTSEL mode and disable MSC interface (the 1 here) 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) { @@ -2329,7 +2377,9 @@ int main(int argc, char **argv) { // again is to assume it is the only now visible device. settings.force = false; settings.address = -1; - settings.bus = -1; + if (settings.serial.empty()) { + settings.bus = -1; + } continue; } }