esp_usb_jtag: disambiguate multiple boards via --busdev-num and --usb-serial-num

libusb_open_device_with_vid_pid() returns the first VID:PID match, so
with two ESP32-S3 cables connected, every invocation programmed the
same board. Replace it with a libusb_get_device_list() iteration that
also honours cable.bus_addr/device_addr (already exposed as
--busdev-num) and --usb-serial-num flag that matches the device
iSerialNumber by substring (useful with MAC-derived serials).

The -d /dev/ttyACM* path could not help: that's the CDC-ACM interface
(iface 0), while JTAG is on the vendor iface 2 reached via libusb;
ttyACM numbering and libusb enumeration order are independent.

Constraint: must keep existing single-board invocations working without flag changes
Confidence: high
Scope-risk: narrow
Directive: arguments struct uses positional aggregate init at main.cpp:120; new fields must add a matching slot
Not-tested: simultaneous two-board programming on real hardware (single-board path verified to build)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
r4d10n 2026-05-14 14:22:15 +05:30 committed by Gwenhael Goavec-Merou
parent 4000496abc
commit 9e548983f9
5 changed files with 79 additions and 17 deletions

View File

@ -70,7 +70,9 @@ openFPGALoader -- a program to flash FPGA
--cable-index arg probe index (FTDI and cmsisDAP)
--busdev-num arg select a probe by it bus and device number
(bus_num:device_addr)
--ftdi-serial arg FTDI chip serial number
--usb-serial-num arg USB iSerial (FTDI chip serial number or ESP32
iSerialNumber substring)
--ftdi-serial arg FTDI chip serial number (Deprecated)
--ftdi-channel arg FTDI chip channel number (channels 0-3 map to
A-D)
-d, --device arg device to use (/dev/ttyUSBx)

View File

@ -245,13 +245,17 @@ static uint16_t esp_usb_target_chip_id = 0; /* not applicable for FPGA, they hav
/* end copy from openocd */
esp_usb_jtag::esp_usb_jtag(uint32_t clkHZ, int8_t verbose, int vid = ESPUSBJTAG_VID, int pid = ESPUSBJTAG_PID):
esp_usb_jtag::esp_usb_jtag(uint32_t clkHZ, int8_t verbose,
int vid = ESPUSBJTAG_VID, int pid = ESPUSBJTAG_PID,
uint8_t bus_addr = 0, uint8_t dev_addr = 0,
const std::string &serial = ""):
_verbose(verbose > 1),
dev_handle(NULL), usb_ctx(NULL), _tdi(0), _tms(0),
/* Default for emard firmware. */
_esp_usb_jtag_caps(0x2000), _write_ep(0x02),
_vid(ESPUSBJTAG_VID), _pid(ESPUSBJTAG_PID)
{
libusb_device **devs = NULL;
int ret;
char mess[256];
@ -260,18 +264,61 @@ esp_usb_jtag::esp_usb_jtag(uint32_t clkHZ, int8_t verbose, int vid = ESPUSBJTAG_
throw std::exception();
}
dev_handle = libusb_open_device_with_vid_pid(usb_ctx,
_vid, _pid);
if (!dev_handle) {
_esp_usb_jtag_caps = 0x030A;
_write_ep = 0x03;
_pid = 0x1002;
dev_handle = libusb_open_device_with_vid_pid(usb_ctx,
_vid, _pid);
ssize_t cnt = libusb_get_device_list(usb_ctx, &devs);
if (cnt < 0) {
std::cerr << "esp_usb_jtag: libusb_get_device_list failed: "
<< libusb_error_name(static_cast<int>(cnt)) << std::endl;
libusb_exit(usb_ctx);
throw std::exception();
}
for (ssize_t i = 0; i < cnt; i++) {
libusb_device *dev = devs[i];
struct libusb_device_descriptor desc;
if (libusb_get_device_descriptor(dev, &desc) < 0)
continue;
if (desc.idVendor != vid || desc.idProduct != pid)
continue;
/* bus/device filter (only when both are user-supplied) */
if (bus_addr != 0 && dev_addr != 0 &&
(libusb_get_bus_number(dev) != bus_addr ||
libusb_get_device_address(dev) != dev_addr))
continue;
/* serial filter */
if (!serial.empty()) {
libusb_device_handle *probe = NULL;
if (libusb_open(dev, &probe) < 0)
continue;
unsigned char raw[256] = {0};
int n = 0;
if (desc.iSerialNumber)
n = libusb_get_string_descriptor_ascii(probe,
desc.iSerialNumber, raw, sizeof(raw));
libusb_close(probe);
if (n <= 0)
continue;
const std::string found(reinterpret_cast<char *>(raw), n);
if (found.find(serial) == std::string::npos)
continue;
}
if (libusb_open(dev, &dev_handle) == 0)
break;
dev_handle = NULL;
}
libusb_free_device_list(devs, 1);
if (!dev_handle) {
snprintf(mess, 256, "fails to open esp_usb_jtag device");
printError(mess);
std::cerr << "fails to open esp_usb_jtag device vid:pid 0x"
<< std::hex << vid << ":0x" << pid;
if (bus_addr || dev_addr)
std::cerr << " bus:dev " << std::dec << static_cast<int>(bus_addr)
<< ":" << static_cast<int>(dev_addr);
if (!serial.empty())
std::cerr << " serial '" << serial << "'";
std::cerr << std::endl;
libusb_exit(usb_ctx);
throw std::exception();
}

View File

@ -12,6 +12,8 @@
#include <libusb.h>
#include <string>
#include "jtagInterface.hpp"
/*!
@ -23,7 +25,9 @@
class esp_usb_jtag : public JtagInterface {
public:
esp_usb_jtag(uint32_t clkHZ, int8_t verbose, int vid, int pid);
esp_usb_jtag(uint32_t clkHZ, int8_t verbose, int vid, int pid,
uint8_t bus_addr, uint8_t dev_addr,
const std::string &serial);
virtual ~esp_usb_jtag();
int setClkFreq(uint32_t clkHZ) override;

View File

@ -165,7 +165,8 @@ Jtag::Jtag(const cable_t &cable, const jtag_pins_conf_t *pin_conf,
break;
case MODE_ESP:
#ifdef ENABLE_ESP_USB
_jtag = new esp_usb_jtag(clkHZ, verbose, 0x303a, 0x1001);
_jtag = new esp_usb_jtag(clkHZ, verbose, 0x303a, 0x1001,
cable.bus_addr, cable.device_addr, serial);
#else
std::cerr << "Jtag: support for esp32s3 cable was not enabled at compile time" << std::endl;
throw std::exception();

View File

@ -272,6 +272,14 @@ int main(int argc, char **argv)
}
#endif
if (!args.usb_serial_num.empty()) {
if (cable.type != MODE_FTDI_SERIAL && cable.type != MODE_FTDI_BITBANG &&
cable.type != MODE_ESP){
printError("Error: usb-serial-num param is for FTDI and esp32s3 cables.");
return EXIT_FAILURE;
}
}
if (args.vid != 0) {
printInfo("Cable VID overridden");
cable.vid = args.vid;
@ -921,7 +929,7 @@ int parse_opt(int argc, char **argv, struct arguments *args,
("busdev-num",
"select a probe by it bus and device number (bus_num:device_addr)",
cxxopts::value<std::vector<std::string>>(bus_dev_num))
("usb-serial-num", "USB iSerial (FTDI chip serial number)",
("usb-serial-num", "USB iSerial (FTDI chip serial number or ESP32 iSerialNumber substring)",
cxxopts::value<std::string>(args->usb_serial_num))
("ftdi-serial", "FTDI chip serial number (Deprecated)",
cxxopts::value<std::string>(ftdi_serial))
@ -1131,8 +1139,8 @@ int parse_opt(int argc, char **argv, struct arguments *args,
}
if (result.count("ftdi-serial")) {
if (result.count("usb_serial_num")) {
printError("Error: ftdi_serial and usb_serial_num can't be used at the same time.");
if (result.count("usb-serial-num")) {
printError("Error: ftdi-serial and usb-serial-num can't be used at the same time.");
return -1;
}
args->usb_serial_num = ftdi_serial;