diff --git a/README.md b/README.md index fffa32c..8c7451a 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 SYNOPSYS: - 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 @@ -145,7 +147,6 @@ TARGET SELECTION: The file name -t Specify file type (uf2 | elf | bin) explicitly, ignoring file extension - ``` Note the -f arguments vary slightly for Windows vs macOS / Unix platforms. @@ -241,6 +242,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 157174f..e2599c5 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,7 @@ struct _settings { uint32_t binary_start = FLASH_START; int bus=-1; int address=-1; + string serial; uint32_t offset = 0; uint32_t from = 0; uint32_t to = 0; @@ -295,12 +297,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); @@ -1581,28 +1596,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) { @@ -2179,6 +2191,20 @@ 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) { + unsigned char data[33]; + if (libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, data, 31)) { + data[32] = '\0'; + return reinterpret_cast(data); + } + } + + return ""; +} + int main(int argc, char **argv) { libusb_context *ctx = nullptr; @@ -2229,6 +2255,7 @@ int main(int argc, char **argv) { if (handle) { to_close.push_back(handle); } + if (!settings.serial.empty() && (!handle || settings.serial != get_usb_device_serial(*dev, handle))) continue; if (result != dr_error) { devices[result].push_back(std::make_pair(*dev, handle)); }