From fa1d328b934dd77e1a284410500984363653325c Mon Sep 17 00:00:00 2001 From: Emard Date: Mon, 30 Dec 2024 09:50:31 +0100 Subject: [PATCH 1/4] esp_usb_jtag: new cable - copy dirtyjtag to esp_usb_jtag, it compiles - copy protocol definiton, defines and private data/struct from openocd esp_usb_jtag.c - ulx3s_esp board with esp32s3 cable - esp_usb_jtag specify usb vid:pid in jtag.cpp - hardcode usb interface and endpoints - getting caps - set chip id (not applicable for fpga) - tms write done, untested - cleanup and toggle clk - 32bit counting - setting divisor (todo read base freq) - div range within 1-255 - base speed from descriptor - fix doc typo with swapped tms/tdi some cleanup but it doesn't work. --- CMakeLists.txt | 2 + src/board.hpp | 1 + src/cable.hpp | 2 + src/esp_usb_jtag.cpp | 716 +++++++++++++++++++++++++++++++++++++++++++ src/esp_usb_jtag.hpp | 63 ++++ src/jtag.cpp | 4 + 6 files changed, 788 insertions(+) create mode 100644 src/esp_usb_jtag.cpp create mode 100644 src/esp_usb_jtag.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 05c78d7..9eb56bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,6 +134,7 @@ set(OPENFPGALOADER_SOURCE src/xilinxMapParser.cpp src/colognechip.cpp src/colognechipCfgParser.cpp + src/esp_usb_jtag.cpp ) set(OPENFPGALOADER_HEADERS @@ -188,6 +189,7 @@ set(OPENFPGALOADER_HEADERS src/xilinxMapParser.hpp src/colognechip.hpp src/colognechipCfgParser.hpp + src/esp_usb_jtag.hpp ) link_directories( diff --git a/src/board.hpp b/src/board.hpp index 00abb85..1ce77a9 100644 --- a/src/board.hpp +++ b/src/board.hpp @@ -237,6 +237,7 @@ static std::map board_list = { JTAG_BITBANG_BOARD("ulx3s", "", "ft231X", 0, 0, FT232RL_DCD, FT232RL_DSR, FT232RL_RI, FT232RL_CTS, CABLE_DEFAULT), DFU_BOARD("ulx3s_dfu", "", "dfu", 0x1d50, 0x614b, 0), + JTAG_BOARD("ulx3s_esp", "", "esp32s3", 0, 0, CABLE_DEFAULT), JTAG_BOARD("usrpx300", "xc7k325tffg900", "digilent", 0, 0, CABLE_MHZ(15)), JTAG_BOARD("usrpx310", "xc7k410tffg900", "digilent", 0, 0, CABLE_MHZ(15)), JTAG_BOARD("vec_v6", "xc6vlx130tff784", "ft2232", 0, 0, CABLE_DEFAULT), diff --git a/src/cable.hpp b/src/cable.hpp index 8729adb..9e4f2a5 100644 --- a/src/cable.hpp +++ b/src/cable.hpp @@ -29,6 +29,7 @@ enum communication_type { MODE_REMOTEBITBANG, /*! Remote Bitbang mode */ MODE_CH347, /*! CH347 JTAG mode */ MODE_GWU2X, /*! Gowin GWU2X JTAG mode */ + MODE_ESP, /*! esp32c3, esp32s3 */ }; /*! @@ -101,6 +102,7 @@ static std::map cable_list = { {"digilent_hs3", FTDI_SER(0x0403, 0x6014, FTDI_INTF_A, 0x88, 0x8B, 0x20, 0x30)}, {"digilent_ad", FTDI_SER(0x0403, 0x6014, FTDI_INTF_A, 0x08, 0x0B, 0x80, 0x80)}, {"dirtyJtag", CABLE_DEF(MODE_DIRTYJTAG, 0x1209, 0xC0CA )}, + {"esp32s3", CABLE_DEF(MODE_ESP, 0x303a, 0x1001 )}, {"efinix_spi_ft4232", FTDI_SER(0x0403, 0x6011, FTDI_INTF_A, 0x08, 0x8B, 0x00, 0x00)}, {"efinix_jtag_ft4232", FTDI_SER(0x0403, 0x6011, FTDI_INTF_B, 0x08, 0x8B, 0x00, 0x00)}, {"efinix_spi_ft2232", FTDI_SER(0x0403, 0x6010, FTDI_INTF_A, 0x08, 0x8B, 0x00, 0x00)}, diff --git a/src/esp_usb_jtag.cpp b/src/esp_usb_jtag.cpp new file mode 100644 index 0000000..cf32853 --- /dev/null +++ b/src/esp_usb_jtag.cpp @@ -0,0 +1,716 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2024 EMARD + */ + +/* +Holy Crap, it's protocol documentation, and it's even vendor-provided! + +A device that speaks this protocol has two endpoints intended for JTAG debugging: one +OUT for the host to send encoded commands to, one IN from which the host can read any read +TDO bits. The device will also respond to vendor-defined interface requests on ep0. + +The main communication method is over the IN/OUT endpoints. The commands that are expected +on the OUT endpoint are one nibble wide and are processed high-nibble-first, low-nibble-second, +and in the order the bytes come in. Commands are defined as follows: + + bit 3 2 1 0 +CMD_CLK [ 0 cap tms tdi ] +CMD_RST [ 1 0 0 srst ] +CMD_FLUSH [ 1 0 1 0 ] +CMD_RSV [ 1 0 1 1 ] +CMD_REP [ 1 1 R1 R0 ] + +CMD_CLK sets the TDI and TMS lines to the value of `tdi` and `tms` and lowers, then raises, TCK. If +`cap` is 1, the value of TDO is captured and can be retrieved over the IN endpoint. The bytes read from +the IN endpoint specifically are these bits, with the lowest bit in every byte captured first and the +bytes returned in the order the data in them was captured. The durations of TCK being high / low can +be set using the VEND_JTAG_SETDIV vendor-specific interface request. + +CMD_RST controls the SRST line; as soon as the command is processed, the SRST line will be set +to the value of `srst`. + +CMD_FLUSH flushes the IN endpoint; zeroes will be added to the amount of bits in the endpoint until +the payload is a multiple of bytes, and the data is offered to the host. If the IN endpoint has +no data, this effectively becomes a no-op; the endpoint won't send any 0-byte payloads. + +CMD_RSV is reserved for future use. + +CMD_REP repeats the last command that is not CMD_REP. The amount of times a CMD_REP command will +re-execute this command is (r1*2+r0)<<(2*n), where n is the amount of previous repeat commands executed +since the command to be repeated. + +An example for CMD_REP: Say the host queues: +1. CMD_CLK - This will execute one CMD_CLK. +2. CMD_REP with r1=0 and r0=1 - This will execute 1. another (0*2+1)<<(2*0)=1 time. +3. CMD_REP with r1=1 and r0=0 - This will execute 1. another (1*2+0)<<(2*1)=4 times. +4. CMD_REP with r1=0 and r0=1 - This will execute 1. another (0*2+1)<<(2*2)=8 time. +5. CMD_FLUSH - This will flush the IN pipeline. +6. CMD_CLK - This will execute one CMD_CLK +7. CMD_REP with r1=1 and r0=0 - This will execute 6. another (1*2+0)<<(2*0)=2 times. +8. CMD_FLUSH - This will flush the IN pipeline. + +Note that the net effect of the repetitions is that command 1 is executed (1+1+4+8=) 14 times and +command 6 is executed (1+2=) 3 times. + +Note that the device only has a fairly limited amount of endpoint RAM. It's probably best to keep +an eye on the amount of bytes that are supposed to be in the IN endpoint and grab those before stuffing +more commands into the OUT endpoint: the OUT endpoint will not accept any more commands (writes will +time out) when the IN endpoint buffers are all filled up. + +The device also supports some vendor-specific interface requests. These requests are sent as control +transfers on endpoint 0 to the JTAG endpoint. Note that these commands bypass the data in the OUT +endpoint; if timing is important, it's important that this endpoint is empty. This can be done by +e.g sending one CMD_CLK capturing TDI, then one CMD_FLUSH, then waiting until the bit appears on the +IN endpoint. + +bmRequestType bRequest wValue wIndex wLength Data +01000000b VEND_JTAG_SETDIV [divide] interface 0 None +01000000b VEND_JTAG_SETIO [iobits] interface 0 None +11000000b VEND_JTAG_GETTDO 0 interface 1 [iostate] +10000000b GET_DESCRIPTOR(6) 0x2000 0 256 [jtag cap desc] + +VEND_JTAG_SETDIV indirectly controls the speed of the TCK clock. The value written here is the length +of a TCK cycle, in ticks of the adapters base clock. Both the base clock value as well as the +minimum and maximum divider can be read from the jtag capabilities descriptor, as explained +below. Note that this should not be set to a value outside of the range described there, +otherwise results are undefined. + +VEND_JTAG_SETIO can be controlled to directly set the IO pins. The format of [iobits] normally is +{11'b0, srst, trst, tck, tms, tdi} +Note that the first 11 0 bits are reserved for future use, current hardware ignores them. + +VEND_JTAG_GETTDO returns one byte, of which bit 0 indicates the current state of the TDO input. +Note that other bits are reserved for future use and should be ignored. + +To describe the capabilities of the JTAG adapter, a specific descriptor (0x20) can be retrieved. +The format of the descriptor documented below. The descriptor works in the same fashion as USB +descriptors: a header indicating the version and total length followed by descriptors with a +specific type and size. Forward compatibility is guaranteed as software can skip over an unknown +descriptor. + +this description is a copy from openocd/src/jtag/drivers/esp_usb_jtag.c +*/ + + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "esp_usb_jtag.hpp" +#include "display.hpp" + +using namespace std; + +#define ESPUSBJTAG_VID 0x303A +#define ESPUSBJTAG_PID 0x1001 + +#define ESPUSBJTAG_INTF 2 +#define ESPUSBJTAG_WRITE_EP 0x02 +#define ESPUSBJTAG_READ_EP 0x83 + +#define ESPUSBJTAG_TIMEOUT_MS 1000 + +/* begin copy from openocd */ + +#define JTAG_PROTO_CAPS_VER 1 /* Version field. At the moment, only version 1 is defined. */ +struct jtag_proto_caps_hdr { + uint8_t proto_ver; /* Protocol version. Expects JTAG_PROTO_CAPS_VER for now. */ + uint8_t length; /* of this plus any following descriptors */ +} __attribute__((packed)); + +/* start of the descriptor headers */ +#define JTAG_BUILTIN_DESCR_START_OFF 0 /* Devices with builtin usb jtag */ +/* +* ESP USB Bridge https://github.com/espressif/esp-usb-bridge uses string descriptor. +* Skip 1 byte length and 1 byte descriptor type +*/ +#define JTAG_EUB_DESCR_START_OFF 2 /* ESP USB Bridge */ + +/* +Note: At the moment, there is only a speed_caps version indicating the base speed of the JTAG +hardware is derived from the APB bus speed of the SoC. If later on, there are standalone +converters using the protocol, we should define e.g. JTAG_PROTO_CAPS_SPEED_FIXED_TYPE to distinguish +between the two. + +Note: If the JTAG device has larger buffers than endpoint-size-plus-a-bit, we should have some kind +of caps header to assume this. If no such caps exist, assume a minimum (in) buffer of endpoint size + 4. +*/ + +struct jtag_gen_hdr { + uint8_t type; + uint8_t length; +} __attribute__((packed)); + +struct jtag_proto_caps_speed_apb { + uint8_t type; /* Type, always JTAG_PROTO_CAPS_SPEED_APB_TYPE */ + uint8_t length; /* Length of this */ + uint8_t apb_speed_10khz[2]; /* ABP bus speed, in 10KHz increments. Base speed is half this. */ + uint8_t div_min[2]; /* minimum divisor (to base speed), inclusive */ + uint8_t div_max[2]; /* maximum divisor (to base speed), inclusive */ +} __attribute__((packed)); + +#define JTAG_PROTO_CAPS_DATA_LEN 255 +#define JTAG_PROTO_CAPS_SPEED_APB_TYPE 1 + +#define VEND_DESCR_BUILTIN_JTAG_CAPS 0x2000 + +#define VEND_JTAG_SETDIV 0 +#define VEND_JTAG_SETIO 1 +#define VEND_JTAG_GETTDO 2 +#define VEND_JTAG_SET_CHIPID 3 + +#define BIT(x) (1< (unsigned int)jtag_caps_read_len) { + cerr << "esp_usb_jtag: not enough data to get header" << endl; + // goto out; + } + + struct jtag_proto_caps_hdr *hdr = (struct jtag_proto_caps_hdr *)&jtag_caps_desc[p]; + if (hdr->proto_ver != JTAG_PROTO_CAPS_VER) { + cerr << "esp_usb_jtag: unknown jtag_caps descriptor version 0x" << std::hex + << hdr->proto_ver << endl; + // goto out; + } + if (hdr->length > jtag_caps_read_len) { + cerr << "esp_usb_jtag: header length (" << hdr->length + << ") bigger then max read bytes (" << jtag_caps_read_len + << ")" << endl; + // goto out; + } + + /* TODO: grab from (future) descriptor if we ever have a device with larger IN buffers */ + // priv->hw_in_fifo_len = 4; + + p += sizeof(struct jtag_proto_caps_hdr); + while (p + sizeof(struct jtag_gen_hdr) < hdr->length) { + struct jtag_gen_hdr *dhdr = (struct jtag_gen_hdr *)&jtag_caps_desc[p]; + if (dhdr->type == JTAG_PROTO_CAPS_SPEED_APB_TYPE) { + if (p + sizeof(struct jtag_proto_caps_speed_apb) < hdr->length) { + cerr << "esp_usb_jtag: not enough data to get caps speed" << endl; + return false; + } + struct jtag_proto_caps_speed_apb *spcap = (struct jtag_proto_caps_speed_apb *)dhdr; + /* base speed always is half APB speed */ + _base_speed_khz = (spcap->apb_speed_10khz[0] + 256 * spcap->apb_speed_10khz[1]) * 10 / 2; + _div_min = spcap->div_min[0] + 256 * spcap->div_min[1]; + _div_max = spcap->div_max[0] + 256 * spcap->div_max[1]; + /* TODO: mark in priv that this is apb-derived and as such may change if apb + * ever changes? */ + } else { + cerr << "esp_usb_jtag: unknown caps type 0x" << dhdr->type << endl;; + } + p += dhdr->length; + } + if (priv->base_speed_khz == UINT32_MAX) { + cerr << "esp_usb_jtag: No speed caps found... using sane-ish defaults." << endl; + _base_speed_khz = 1000; + } + cerr << "esp_usb_jtag: Device found. Base speed " << std::dec << _base_speed_khz << " KHz, div range " << (int)_div_min << " to " << (int)_div_max << endl; + + _version = 1; // currently only protocol version 1 exists + + /* inform bridge board about the connected target chip for the specific operations + * it is also safe to send this info to chips that have builtin usb jtag */ + libusb_control_transfer(dev_handle, + /*type*/ LIBUSB_REQUEST_TYPE_VENDOR, + /*brequest*/ VEND_JTAG_SET_CHIPID, + /*wvalue*/ esp_usb_target_chip_id, + /*interface*/ 0, + /*data*/ NULL, + /*length*/ 0, + /*timeout ms*/ ESPUSBJTAG_TIMEOUT_MS); + + return true; +} + + + +int esp_usb_jtag::setClkFreq(uint32_t clkHZ) +{ + int actual_length; + int ret = 0, req_freq = clkHZ; + + uint32_t base_speed_Hz = _base_speed_khz * 1000; // TODO read base speed from caps + + if (clkHZ > base_speed_Hz) { + printWarn("esp_usb_jtag probe limited to %d kHz", _base_speed_khz); + clkHZ = base_speed_Hz; + } + + uint16_t divisor = base_speed_Hz / clkHZ; + + _clkHZ = clkHZ; + + printInfo("Jtag frequency : requested " + std::to_string(req_freq) + + "Hz -> real " + std::to_string(clkHZ) + "Hz divisor=" + std::to_string(divisor)); + + ret = libusb_control_transfer(dev_handle, + /*type*/ LIBUSB_REQUEST_TYPE_VENDOR, + /*brequest*/ VEND_JTAG_SETDIV, + /*wvalue*/ divisor, + /*windex*/ 0, + /*data*/ NULL, + /*length*/ 0, + /*timeout ms*/ ESPUSBJTAG_TIMEOUT_MS); + + if (ret != 0) { + cerr << "setClkFreq: usb bulk write failed " << ret << endl; + return -EXIT_FAILURE; + } + + return clkHZ; +} + +int esp_usb_jtag::writeTMS(const uint8_t *tms, uint32_t len, + __attribute__((unused)) bool flush_buffer, + __attribute__((unused)) const uint8_t tdi) +{ + uint8_t buf[OUT_BUF_SZ]; + int transferred_length; // never used + + if(flush_buffer) + flush(); + + if (len == 0) + return 0; + + for(int i = 0; i < (len+7)>>3; i++) + cerr << " " << std::hex << (int)tms[i]; + cerr << endl; + + uint8_t prev_high_nibble = CMD_FLUSH << 4; // for odd length 1st command is flush = nop + uint32_t buffer_idx = 0; // reset + uint8_t is_high_nibble = 1 & ~len; + // for even len: start with is_high_nibble = 1 + // for odd len: start with is_high_nibble = 0 + // 1st (high nibble) is flush = nop + // 2nd (low nibble) is data + // last byte in buf will have data in both nibbles, no flush + // exec order: high-nibble-first, low-nibble-second + for (uint32_t i = 0; i < len; i++) + { + uint8_t tms_bit = (tms[i>>3] >> (i&7)) & 1; // get i'th bit from tms + uint8_t cmd = CMD_CLK(0, 0, tms_bit); + if(is_high_nibble) + { // 1st (high nibble) = data + buf[buffer_idx] = prev_high_nibble = cmd << 4; + is_high_nibble = 0; + } + else // low nibble + { // 2nd (low nibble) = data, keep prev high nibble + buf[buffer_idx] = prev_high_nibble | cmd; + buffer_idx++; // byte complete, advance to the next byte in buf + is_high_nibble = 1; + } + + if (buffer_idx >= sizeof(buf) /*buf full*/ || i == len - 1 /*last*/) + { + int ret = libusb_bulk_transfer(dev_handle, + /*endpoint*/ ESPUSBJTAG_WRITE_EP, + /*data*/ buf, + /*length*/ buffer_idx, + /*transf.len*/ &transferred_length, + /*timeout ms*/ ESPUSBJTAG_TIMEOUT_MS); + if (ret != 0) + { + cerr << "writeTMS: usb bulk write failed " << ret << endl; + return -EXIT_FAILURE; + } + cerr << "tms" << endl; + buffer_idx = 0; // reset + } + } + return len; +} + +int esp_usb_jtag::toggleClk(uint8_t tms, uint8_t tdi, uint32_t len) +{ + uint8_t buf[OUT_BUF_SZ]; + int transferred_length; // never used + + if (len == 0) + return 0; + + uint8_t prev_high_nibble = CMD_FLUSH << 4; // for odd length 1st command is flush = nop + uint32_t buffer_idx = 0; // reset + uint8_t is_high_nibble = 1 & ~len; + // for even len: start with is_high_nibble = 1 + // for odd len: start with is_high_nibble = 0 + // 1st (high nibble) is flush = nop + // 2nd (low nibble) is data + // last byte in buf will have data in both nibbles, no flush + // exec order: high-nibble-first, low-nibble-second + uint8_t cmd = CMD_CLK(0, tdi, tms); + for (uint32_t i = 0; i < len; i++) + { + // TODO: repeat clocking with CMD_REP + if(is_high_nibble) + { // 1st (high nibble) = cmd + buf[buffer_idx] = prev_high_nibble; + is_high_nibble = 0; + } + else // low nibble + { // 2nd (low nibble) = cmd, keep prev high nibble + buf[buffer_idx] = prev_high_nibble | cmd; + buffer_idx++; // byte complete, advance to the next byte in buf + is_high_nibble = 1; + } + + if (buffer_idx >= sizeof(buf) /*buf full*/ || i == len - 1 /*last*/) + { + int ret = libusb_bulk_transfer(dev_handle, + /*endpoint*/ ESPUSBJTAG_WRITE_EP, + /*data*/ buf, + /*length*/ buffer_idx, + /*transferred length*/ &transferred_length, + /*timeout ms*/ ESPUSBJTAG_TIMEOUT_MS); + if (ret != 0) + { + cerr << "toggleClk: usb bulk write failed " << ret << endl; + return -EXIT_FAILURE; + } + cerr << "clk" << endl; + buffer_idx = 0; // reset + } + } + return EXIT_SUCCESS; +} + +int esp_usb_jtag::setio(int srst, int tms, int tdi, int tck) +{ + uint16_t wvalue = ((1&srst)<<3) | ((1&tck)<<2) | ((1&tms)<<1) | (1&tdi); + int ret = libusb_control_transfer(dev_handle, + /*type*/ LIBUSB_REQUEST_TYPE_VENDOR, + /*brequest*/ VEND_JTAG_SETIO, + /*wvalue*/ wvalue, + /*interface*/ 0, + /*data*/ NULL, + /*length*/ 0, + /*timeout ms*/ ESPUSBJTAG_TIMEOUT_MS); + + if (ret != 0) + { + cerr << "setio: control write failed " << ret << endl; + return -EXIT_FAILURE; + } + cerr << "setio 0x" << std::hex << wvalue << endl; + return 0; +} + +int esp_usb_jtag::flush() +{ + uint8_t buf[1] = { (CMD_FLUSH<<3) | CMD_FLUSH }; + int transferred_length; // never used + int ret = libusb_bulk_transfer(dev_handle, + /*endpoint*/ ESPUSBJTAG_WRITE_EP, + /*data*/ buf, + /*length*/ sizeof(buf), + /*transferred length*/ &transferred_length, + /*timeout ms*/ ESPUSBJTAG_TIMEOUT_MS); + if (ret != 0) + { + cerr << "flush: usb bulk write failed " << ret << endl; + return -EXIT_FAILURE; + } + cerr << "flush" << endl; + return 0; +} + +void esp_usb_jtag::drain_in() +{ + uint8_t dummy_rx[64]; + int transferred_length = 1; + while(transferred_length > 0) + { + transferred_length = 0; + libusb_bulk_transfer(dev_handle, + /*endpoint*/ ESPUSBJTAG_READ_EP, + /*data*/ dummy_rx, + /*length*/ sizeof(dummy_rx), + /*transferred length*/ &transferred_length, + /*timeout ms*/ ESPUSBJTAG_TIMEOUT_MS); + } + cerr << "drain_in" << endl; +} + +// TODO +// [ ] odd len +// [ ] end (DR_SHIFT, IR_SHIFT) +int esp_usb_jtag::writeTDI(const uint8_t *tx, uint8_t *rx, uint32_t len, bool end) +{ + int ret; + uint8_t tx_buf[OUT_EP_SZ]; + memset(rx, 0, len>>3); + #if 0 + // for debug force IDCODE 0x12345678 returned + if(len >= 4) memcpy(rx, "\x78\x56\x34\x12", 4); + return EXIT_SUCCESS; + #endif + uint32_t real_bit_len = len - (end ? 1 : 0); + // uint32_t kRealByteLen = (len + 7) / 8; + int transferred_length; + + if (len == 0) + return 0; + + cerr << "real_bit_len=0x" << real_bit_len << endl; + + // drain_in(); + uint8_t prev_high_nibble = CMD_FLUSH << 4; // for odd length 1st command is flush = nop + uint32_t tx_buffer_idx = 0; // reset + uint8_t is_high_nibble = 1 & ~real_bit_len; + // for even len: start with is_high_nibble = 1 + // for odd len: start with is_high_nibble = 0 + // 1st (high nibble) is flush = nop + // 2nd (low nibble) is data + // last byte in buf will have data in both nibbles, no flush + // exec order: high-nibble-first, low-nibble-second + cerr << "is high nibble=" << (int)is_high_nibble << endl; + int bits_in_tx_buf = 0; + for(int i = 0; i < (real_bit_len+7)>>3; i++) + cerr << " " << std::hex << (int)tx[i]; + cerr << endl; + cerr << "tdi_bits "; + for (uint32_t i = 0; i < real_bit_len; i++) + { + uint8_t tdi_bit = (tx[i>>3] >> (i&7)) & 1; // get i'th bit from rx + cerr << (int)tdi_bit; + uint8_t cmd = CMD_CLK(/*tdo*/1, /*tdi*/tdi_bit, /*tms*/0); // with TDO capture + if(is_high_nibble) + { // 1st (high nibble) = data + tx_buf[tx_buffer_idx] = prev_high_nibble = cmd << 4; + is_high_nibble = 0; + } + else // low nibble + { // 2nd (low nibble) = data, keep prev high nibble + tx_buf[tx_buffer_idx++] = prev_high_nibble | cmd; + is_high_nibble = 1; + } + bits_in_tx_buf++; + if (tx_buffer_idx >= sizeof(tx_buf) /*buf full*/ || i >= real_bit_len - 1 /*last*/) + { + cerr << endl << "writeTDI: write_ep len bytes=0x" << tx_buffer_idx << endl; + for(int j = 0; j < tx_buffer_idx; j++) + cerr << " " << std::hex << (int)tx_buf[j]; + cerr << endl; + ret = libusb_bulk_transfer(dev_handle, + /*endpoint*/ ESPUSBJTAG_WRITE_EP, + /*data*/ tx_buf, + /*length*/ tx_buffer_idx, + /*transferred length*/ &transferred_length, + /*timeout ms*/ ESPUSBJTAG_TIMEOUT_MS); + if (ret != 0) + { + cerr << "writeTDI: usb bulk write failed " << ret << endl; + return -EXIT_FAILURE; + } + cerr << "writeTDI write 0x" << tx_buffer_idx << " bytes" << endl; + flush(); // must flush before reading + // TODO support odd len for TDO + // currently only even len TDO works correctly + // for odd len first command sent is CMD_FUSH + // so TDI rx_buf will be missing 1 bit + uint16_t read_bit_len = tx_buffer_idx<<1; + // read_bit_len = bits_in_tx_buf; + uint16_t read_byte_len = (read_bit_len+7)>>3; + // cerr << "read_bit_len=" << (int)read_bit_len << " read_byte_len=" << (int)read_byte_len << endl; + // read_byte_len = 1; + int received_bytes = 0; + while(received_bytes < read_byte_len) + { + ret = libusb_bulk_transfer(dev_handle, + /*endpoint*/ ESPUSBJTAG_READ_EP, + /*data*/ &(rx[(i>>3)+received_bytes]), + /*length*/ read_byte_len-received_bytes, + /*transferred length*/ &transferred_length, + /*timeout ms*/ ESPUSBJTAG_TIMEOUT_MS); + if (ret != 0) + { + cerr << "writeTDI: usb bulk read failed " << ret << endl; + // return -EXIT_FAILURE; + break; + } + cerr << "writeTDI read" << endl; + if (read_byte_len != transferred_length) + { + cerr << "writeTDI: usb bulk read expected=" << read_byte_len << " received=" << transferred_length << endl; + // return -EXIT_FAILURE; + break; + } + received_bytes += transferred_length; + } + tx_buffer_idx = 0; // reset + bits_in_tx_buf = 0; + } + } + + #if 0 + if(end) + { + // TODO support end (DR_SHIFT, IR_SHIFT) + } + #endif + return EXIT_SUCCESS; +} diff --git a/src/esp_usb_jtag.hpp b/src/esp_usb_jtag.hpp new file mode 100644 index 0000000..6744af7 --- /dev/null +++ b/src/esp_usb_jtag.hpp @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2024 EMARD + */ + +#ifndef SRC_ESPUSBJTAG_HPP_ +#define SRC_ESPUSBJTAG_HPP_ + +#include + +#include "jtagInterface.hpp" + +/*! + * \file esp_usb_jtag.hpp + * \class esp_usb_jtag + * \brief ESP32C3, ESP32C6 ESP32S2, ESP32S3 hardware USB JTAG + * \author EMARD + */ + +class esp_usb_jtag : public JtagInterface { + public: + esp_usb_jtag(uint32_t clkHZ, int8_t verbose, int vid, int pid); + virtual ~esp_usb_jtag(); + + int setClkFreq(uint32_t clkHZ) override; + + /* TMS */ + int writeTMS(const uint8_t *tms, uint32_t len, bool flush_buffer, const uint8_t tdi = 1) override; + /* TDI */ + int writeTDI(const uint8_t *tx, uint8_t *rx, uint32_t len, bool end) override; + /* clk */ + int toggleClk(uint8_t tms, uint8_t tdo, uint32_t clk_len) override; + + /*! + * \brief return internal buffer size (in byte). + * \return _buffer_size divided by 2 (two byte for clk) and divided by 8 (one + * state == one byte) + */ + int get_buffer_size() override { return 0;} + + bool isFull() override { return false;} + + int flush() override; + + private: + int8_t _verbose; + + // int sendBitBang(uint8_t mask, uint8_t val, uint8_t *read, bool last); + bool getVersion(); + + void drain_in(); + int setio(int srst, int tms, int tdi, int tck); + int gettdo(); + + libusb_device_handle *dev_handle; + libusb_context *usb_ctx; + uint8_t _tdi; + uint8_t _tms; + uint8_t _version; + uint32_t _base_speed_khz; + uint8_t _div_min, _div_max; +}; +#endif // SRC_ESPUSBJTAG_HPP_ diff --git a/src/jtag.cpp b/src/jtag.cpp index 6c306d7..2f14969 100644 --- a/src/jtag.cpp +++ b/src/jtag.cpp @@ -32,6 +32,7 @@ #include "cmsisDAP.hpp" #endif #include "dirtyJtag.hpp" +#include "esp_usb_jtag.hpp" #include "ch347jtag.hpp" #include "part.hpp" #ifdef ENABLE_REMOTEBITBANG @@ -122,6 +123,9 @@ Jtag::Jtag(const cable_t &cable, const jtag_pins_conf_t *pin_conf, case MODE_JLINK: _jtag = new Jlink(clkHZ, verbose, cable.vid, cable.pid); break; + case MODE_ESP: + _jtag = new esp_usb_jtag(clkHZ, verbose, 0x303a, 0x1001); + break; case MODE_USBBLASTER: _jtag = new UsbBlaster(cable, firmware_path, verbose); break; From b7967cf71a4e22e501883125674c6c49bc43fce3 Mon Sep 17 00:00:00 2001 From: Gwenhael Goavec-Merou Date: Wed, 15 Jan 2025 07:18:13 +0100 Subject: [PATCH 2/4] esp_usb_jtag: improves - esp_usb_jtag: added xfer method to handle/simplify libusb_bulk_transfer calls - src/esp_usb_jtag.cpp: simplify some operations - src/esp_usb_jtag.cpp: writeTDI: fixed rx buffer index, added a basic code to handle end transaction - src/esp_usb_jtag.cpp: writeTMS: store and uses _tdi & _tms - src/esp_usb_jtag.cpp: toggleClock: re-uses _tdi/_tms - src/esp_usb_jtag.cpp: writeTDI: ditto - esp_usb_jtag: fixed verbosity level/be more quiet - esp_usb_jtag: fixed writeTDI with end and tms transition: now integrated instead of distinct sequence. Fixed TDI value with tms transition. Working with ECP5 - esp_usb_jtag: added optional parameter to lower timeout error (useful when it's time to flush the device) - esp_usb_jtag: fixed writeTDI when tx is NULL --- src/esp_usb_jtag.cpp | 684 +++++++++++++++++++++++-------------------- src/esp_usb_jtag.hpp | 69 ++--- 2 files changed, 410 insertions(+), 343 deletions(-) diff --git a/src/esp_usb_jtag.cpp b/src/esp_usb_jtag.cpp index cf32853..14769df 100644 --- a/src/esp_usb_jtag.cpp +++ b/src/esp_usb_jtag.cpp @@ -235,10 +235,7 @@ struct esp_usb_jtag_s { * OpenOCD supports multiple JTAG adapters anyway. */ static struct esp_usb_jtag_s esp_usb_jtag_priv; static struct esp_usb_jtag_s *priv = &esp_usb_jtag_priv; -static const char *esp_usb_jtag_serial; -static uint16_t esp_usb_vid = ESPUSBJTAG_VID; -static uint16_t esp_usb_pid = ESPUSBJTAG_PID; static uint16_t esp_usb_jtag_caps = 0x2000; /* capabilites descriptor ID, different esp32 chip may need different value */ static uint16_t esp_usb_target_chip_id = 0; /* not applicable for FPGA, they have chip id 32-bit wide */ @@ -246,7 +243,7 @@ static uint16_t esp_usb_target_chip_id = 0; /* not applicable for FPGA, they hav esp_usb_jtag::esp_usb_jtag(uint32_t clkHZ, int8_t verbose, int vid = ESPUSBJTAG_VID, int pid = ESPUSBJTAG_PID): - _verbose(verbose), + _verbose(verbose > 1), dev_handle(NULL), usb_ctx(NULL), _tdi(0), _tms(0) { int ret; @@ -284,6 +281,7 @@ esp_usb_jtag::esp_usb_jtag(uint32_t clkHZ, int8_t verbose, int vid = ESPUSBJTAG_ esp_usb_jtag::~esp_usb_jtag() { + drain_in(true); // just to be sure try to flush buffer if (dev_handle) libusb_close(dev_handle); if (usb_ctx) @@ -292,9 +290,6 @@ esp_usb_jtag::~esp_usb_jtag() bool esp_usb_jtag::getVersion() { - int actual_length; - int ret; - /* TODO: This is not proper way to get caps data. Two requests can be done. * 1- With the minimum size required to get to know the total length of that struct, * 2- Then exactly the length of that struct. */ @@ -330,13 +325,13 @@ bool esp_usb_jtag::getVersion() struct jtag_proto_caps_hdr *hdr = (struct jtag_proto_caps_hdr *)&jtag_caps_desc[p]; if (hdr->proto_ver != JTAG_PROTO_CAPS_VER) { cerr << "esp_usb_jtag: unknown jtag_caps descriptor version 0x" << std::hex - << hdr->proto_ver << endl; + << hdr->proto_ver << endl; // goto out; } if (hdr->length > jtag_caps_read_len) { cerr << "esp_usb_jtag: header length (" << hdr->length - << ") bigger then max read bytes (" << jtag_caps_read_len - << ")" << endl; + << ") bigger then max read bytes (" << jtag_caps_read_len + << ")" << endl; // goto out; } @@ -353,7 +348,7 @@ bool esp_usb_jtag::getVersion() } struct jtag_proto_caps_speed_apb *spcap = (struct jtag_proto_caps_speed_apb *)dhdr; /* base speed always is half APB speed */ - _base_speed_khz = (spcap->apb_speed_10khz[0] + 256 * spcap->apb_speed_10khz[1]) * 10 / 2; + _base_speed_khz = (spcap->apb_speed_10khz[0] + 256 * spcap->apb_speed_10khz[1]) * 10 / 2; _div_min = spcap->div_min[0] + 256 * spcap->div_min[1]; _div_max = spcap->div_max[0] + 256 * spcap->div_max[1]; /* TODO: mark in priv that this is apb-derived and as such may change if apb @@ -385,332 +380,401 @@ bool esp_usb_jtag::getVersion() return true; } - - int esp_usb_jtag::setClkFreq(uint32_t clkHZ) { - int actual_length; - int ret = 0, req_freq = clkHZ; + int ret = 0, req_freq = clkHZ; - uint32_t base_speed_Hz = _base_speed_khz * 1000; // TODO read base speed from caps + uint32_t base_speed_Hz = _base_speed_khz * 1000; // TODO read base speed from caps - if (clkHZ > base_speed_Hz) { - printWarn("esp_usb_jtag probe limited to %d kHz", _base_speed_khz); - clkHZ = base_speed_Hz; - } - - uint16_t divisor = base_speed_Hz / clkHZ; - - _clkHZ = clkHZ; - - printInfo("Jtag frequency : requested " + std::to_string(req_freq) + - "Hz -> real " + std::to_string(clkHZ) + "Hz divisor=" + std::to_string(divisor)); - - ret = libusb_control_transfer(dev_handle, - /*type*/ LIBUSB_REQUEST_TYPE_VENDOR, - /*brequest*/ VEND_JTAG_SETDIV, - /*wvalue*/ divisor, - /*windex*/ 0, - /*data*/ NULL, - /*length*/ 0, - /*timeout ms*/ ESPUSBJTAG_TIMEOUT_MS); - - if (ret != 0) { - cerr << "setClkFreq: usb bulk write failed " << ret << endl; - return -EXIT_FAILURE; - } - - return clkHZ; -} - -int esp_usb_jtag::writeTMS(const uint8_t *tms, uint32_t len, - __attribute__((unused)) bool flush_buffer, - __attribute__((unused)) const uint8_t tdi) -{ - uint8_t buf[OUT_BUF_SZ]; - int transferred_length; // never used - - if(flush_buffer) - flush(); - - if (len == 0) - return 0; - - for(int i = 0; i < (len+7)>>3; i++) - cerr << " " << std::hex << (int)tms[i]; - cerr << endl; - - uint8_t prev_high_nibble = CMD_FLUSH << 4; // for odd length 1st command is flush = nop - uint32_t buffer_idx = 0; // reset - uint8_t is_high_nibble = 1 & ~len; - // for even len: start with is_high_nibble = 1 - // for odd len: start with is_high_nibble = 0 - // 1st (high nibble) is flush = nop - // 2nd (low nibble) is data - // last byte in buf will have data in both nibbles, no flush - // exec order: high-nibble-first, low-nibble-second - for (uint32_t i = 0; i < len; i++) - { - uint8_t tms_bit = (tms[i>>3] >> (i&7)) & 1; // get i'th bit from tms - uint8_t cmd = CMD_CLK(0, 0, tms_bit); - if(is_high_nibble) - { // 1st (high nibble) = data - buf[buffer_idx] = prev_high_nibble = cmd << 4; - is_high_nibble = 0; - } - else // low nibble - { // 2nd (low nibble) = data, keep prev high nibble - buf[buffer_idx] = prev_high_nibble | cmd; - buffer_idx++; // byte complete, advance to the next byte in buf - is_high_nibble = 1; - } - - if (buffer_idx >= sizeof(buf) /*buf full*/ || i == len - 1 /*last*/) - { - int ret = libusb_bulk_transfer(dev_handle, - /*endpoint*/ ESPUSBJTAG_WRITE_EP, - /*data*/ buf, - /*length*/ buffer_idx, - /*transf.len*/ &transferred_length, - /*timeout ms*/ ESPUSBJTAG_TIMEOUT_MS); - if (ret != 0) - { - cerr << "writeTMS: usb bulk write failed " << ret << endl; - return -EXIT_FAILURE; - } - cerr << "tms" << endl; - buffer_idx = 0; // reset - } - } - return len; -} - -int esp_usb_jtag::toggleClk(uint8_t tms, uint8_t tdi, uint32_t len) -{ - uint8_t buf[OUT_BUF_SZ]; - int transferred_length; // never used - - if (len == 0) - return 0; - - uint8_t prev_high_nibble = CMD_FLUSH << 4; // for odd length 1st command is flush = nop - uint32_t buffer_idx = 0; // reset - uint8_t is_high_nibble = 1 & ~len; - // for even len: start with is_high_nibble = 1 - // for odd len: start with is_high_nibble = 0 - // 1st (high nibble) is flush = nop - // 2nd (low nibble) is data - // last byte in buf will have data in both nibbles, no flush - // exec order: high-nibble-first, low-nibble-second - uint8_t cmd = CMD_CLK(0, tdi, tms); - for (uint32_t i = 0; i < len; i++) - { - // TODO: repeat clocking with CMD_REP - if(is_high_nibble) - { // 1st (high nibble) = cmd - buf[buffer_idx] = prev_high_nibble; - is_high_nibble = 0; - } - else // low nibble - { // 2nd (low nibble) = cmd, keep prev high nibble - buf[buffer_idx] = prev_high_nibble | cmd; - buffer_idx++; // byte complete, advance to the next byte in buf - is_high_nibble = 1; + if (clkHZ > base_speed_Hz) { + printWarn("esp_usb_jtag probe limited to %d kHz", _base_speed_khz); + clkHZ = base_speed_Hz; } - if (buffer_idx >= sizeof(buf) /*buf full*/ || i == len - 1 /*last*/) - { - int ret = libusb_bulk_transfer(dev_handle, - /*endpoint*/ ESPUSBJTAG_WRITE_EP, - /*data*/ buf, - /*length*/ buffer_idx, - /*transferred length*/ &transferred_length, - /*timeout ms*/ ESPUSBJTAG_TIMEOUT_MS); - if (ret != 0) - { - cerr << "toggleClk: usb bulk write failed " << ret << endl; - return -EXIT_FAILURE; - } - cerr << "clk" << endl; - buffer_idx = 0; // reset - } - } - return EXIT_SUCCESS; + uint16_t divisor = base_speed_Hz / clkHZ; + + _clkHZ = clkHZ; + + printInfo("Jtag frequency : requested " + std::to_string(req_freq) + + "Hz -> real " + std::to_string(clkHZ) + "Hz divisor=" + std::to_string(divisor)); + + ret = libusb_control_transfer(dev_handle, + /*type*/ LIBUSB_REQUEST_TYPE_VENDOR, + /*brequest*/ VEND_JTAG_SETDIV, + /*wvalue*/ divisor, + /*windex*/ 0, + /*data*/ NULL, + /*length*/ 0, + /*timeout ms*/ ESPUSBJTAG_TIMEOUT_MS); + + if (ret != 0) { + cerr << "setClkFreq: usb bulk write failed " << ret << endl; + return -EXIT_FAILURE; + } + + return clkHZ; +} + +/* here we needs to loop over until len bits has been sent + * a second loop is required to fill a packet to up to OUT_EP_SZ byte or + * remaining_bits bytes + */ +int esp_usb_jtag::writeTMS(const uint8_t *tms, uint32_t len, bool flush_buffer, + const uint8_t tdi) +{ + uint8_t buf[OUT_BUF_SZ]; + char mess[256]; + if (_verbose) { + snprintf(mess, 256, "writeTMS %d %d", len, flush_buffer); + printSuccess(mess); + } + + if(flush_buffer) + flush(); + + if (len == 0) + return 0; + + // save current tdi as new tdi state + _tdi = tdi & 0x01; + + uint32_t real_len = 0; + for (uint32_t pos = 0; pos < len; pos += real_len) { + const uint32_t remaining_bits = len - pos; // number of bits to write + // select full buffer vs remaining bits + if (remaining_bits < (OUT_EP_SZ * 2)) + real_len = remaining_bits; + else + real_len = OUT_EP_SZ * 2; + + uint8_t prev_high_nibble = CMD_FLUSH << 4; // for odd length 1st command is flush = nop + uint32_t buffer_idx = 0; // reset + uint8_t is_high_nibble = 1 & ~real_len; + // for even len: start with is_high_nibble = 1 + // for odd len: start with is_high_nibble = 0 + // 1st (high nibble) is flush = nop + // 2nd (low nibble) is data + // last byte in buf will have data in both nibbles, no flush + // exec order: high-nibble-first, low-nibble-second + for (uint32_t i = 0; i < real_len; i++) { + const uint32_t idx = i + pos; + _tms = (tms[idx >> 3] >> (idx & 7)) & 1; // get i'th bit from tms + const uint8_t cmd = CMD_CLK(0, _tdi, _tms); + if(is_high_nibble) { // 1st (high nibble) = data + buf[buffer_idx] = prev_high_nibble = cmd << 4; + } else { // low nibble + // 2nd (low nibble) = data, keep prev high nibble + buf[buffer_idx] = prev_high_nibble | cmd; + buffer_idx++; // byte complete, advance to the next byte in buf + } + is_high_nibble ^= 1; // toggle + } + + const int ret = xfer(buf, NULL, buffer_idx); + if (ret < 0) { + snprintf(mess, 256, "ESP USB Jtag: writeTMS failed with error %d", ret); + printError(mess); + return -EXIT_FAILURE; + } + if (_verbose) + cerr << "tms" << endl; + } + + return len; +} + +/* Not only here and not sure it is true: + * when len < OUT_BUF_SZ is_high_nibble is fine: the buffer is filed with + * the full sequence + * when len > OUT_BUF_SZ we have to sent OUT_BUF_SZ x n + remaining bits + * here is_high_nibble must be re-computed more than one time + */ + +/* Here we have to write len bit or 2xlen Bytes + */ +int esp_usb_jtag::toggleClk(uint8_t tms, uint8_t tdi, uint32_t len) +{ + uint8_t buf[OUT_BUF_SZ]; + char mess[256]; + if (_verbose) { + snprintf(mess, 256, "toggleClk %d", len); + printSuccess(mess); + } + + if (len == 0) + return 0; + + _tms = tms; // store tms as new default tms state + _tdi = tdi; // store tdi as new default tdi state + + const uint8_t cmd = CMD_CLK(0, _tdi, _tms); // cmd is constant + const uint8_t prev_high_nibble = (cmd << 4) | cmd; + uint32_t real_len = 0; + /* loop on OUT_BUF_SZ packets + * buffer is able to store OUT_EP_SZ * 2 bit + */ + for (uint32_t pos = 0; pos < len; pos += real_len) { + // Compute number of bits to write + const uint32_t remaining_bits = len - pos; + // select before the full buffer or remaining bits + if (remaining_bits < (OUT_EP_SZ * 2)) + real_len = remaining_bits; + else + real_len = OUT_EP_SZ * 2; + + const uint32_t byte_len = (real_len + 1) >> 1; // Byte len (2bits/bytes, rounded) + + // prepare buffer + memset(buf, prev_high_nibble, byte_len); + + if ((real_len & 0x01) == 1) // padding with CMD_FLUSH + buf[0] = (CMD_FLUSH << 4) | cmd; + + const int ret = xfer(buf, NULL, byte_len); + if (ret < 0) { + snprintf(mess, 256, "ESP USB Jtag: toggleClk failed with error %d", ret); + printError(mess); + return -EXIT_FAILURE; + } + } + return EXIT_SUCCESS; } int esp_usb_jtag::setio(int srst, int tms, int tdi, int tck) { - uint16_t wvalue = ((1&srst)<<3) | ((1&tck)<<2) | ((1&tms)<<1) | (1&tdi); - int ret = libusb_control_transfer(dev_handle, - /*type*/ LIBUSB_REQUEST_TYPE_VENDOR, - /*brequest*/ VEND_JTAG_SETIO, - /*wvalue*/ wvalue, - /*interface*/ 0, - /*data*/ NULL, - /*length*/ 0, - /*timeout ms*/ ESPUSBJTAG_TIMEOUT_MS); + uint16_t wvalue = ((1&srst)<<3) | ((1&tck)<<2) | ((1&tms)<<1) | (1&tdi); + int ret = libusb_control_transfer(dev_handle, + /*type*/ LIBUSB_REQUEST_TYPE_VENDOR, + /*brequest*/ VEND_JTAG_SETIO, + /*wvalue*/ wvalue, + /*interface*/ 0, + /*data*/ NULL, + /*length*/ 0, + /*timeout ms*/ ESPUSBJTAG_TIMEOUT_MS); - if (ret != 0) - { - cerr << "setio: control write failed " << ret << endl; - return -EXIT_FAILURE; - } - cerr << "setio 0x" << std::hex << wvalue << endl; - return 0; + if (ret != 0) { + cerr << "setio: control write failed " << ret << endl; + return -EXIT_FAILURE; + } + if (_verbose) + cerr << "setio 0x" << std::hex << wvalue << endl; + return 0; } int esp_usb_jtag::flush() { - uint8_t buf[1] = { (CMD_FLUSH<<3) | CMD_FLUSH }; - int transferred_length; // never used - int ret = libusb_bulk_transfer(dev_handle, - /*endpoint*/ ESPUSBJTAG_WRITE_EP, - /*data*/ buf, - /*length*/ sizeof(buf), - /*transferred length*/ &transferred_length, - /*timeout ms*/ ESPUSBJTAG_TIMEOUT_MS); - if (ret != 0) - { - cerr << "flush: usb bulk write failed " << ret << endl; - return -EXIT_FAILURE; - } - cerr << "flush" << endl; - return 0; + const uint8_t buf = (CMD_FLUSH << 4) | CMD_FLUSH; + if (_verbose) + printInfo("flush"); + + if (xfer(&buf, NULL, 1) < 0) { + printError("ESP USB Jtag: flush failed"); + return -EXIT_FAILURE; + } + return 0; } -void esp_usb_jtag::drain_in() +void esp_usb_jtag::drain_in(bool is_timeout_fine) { - uint8_t dummy_rx[64]; - int transferred_length = 1; - while(transferred_length > 0) - { - transferred_length = 0; - libusb_bulk_transfer(dev_handle, - /*endpoint*/ ESPUSBJTAG_READ_EP, - /*data*/ dummy_rx, - /*length*/ sizeof(dummy_rx), - /*transferred length*/ &transferred_length, - /*timeout ms*/ ESPUSBJTAG_TIMEOUT_MS); - } - cerr << "drain_in" << endl; + uint8_t dummy_rx[64]; + int ret = 1; + do { + ret = xfer(NULL, dummy_rx, sizeof(dummy_rx), is_timeout_fine); + if (ret < 0) { + printError("ESP USB Jtag drain_in failed"); + return; + } + } while(ret > 0); + if (_verbose) + printInfo("drain_in"); +} + +int esp_usb_jtag::xfer(const uint8_t *tx, uint8_t *rx, const uint16_t length, + bool is_timeout_fine) +{ + char mess[128]; + const bool is_read = (rx != NULL), is_write = (tx != NULL); + if (_verbose) { + snprintf(mess, 128, "xfer: rx: %s tx: %s length %d", + is_read ? "True" : "False", is_write ? "True" : "False", length); + printInfo(mess); + } + const unsigned char endpoint = (is_write) ? ESPUSBJTAG_WRITE_EP : ESPUSBJTAG_READ_EP; + uint8_t *data = (is_write) ? (uint8_t *)tx : rx; + if (is_write && _verbose) { + printf("xfer: write: "); + for (int i = 0; i < length; i++) + printf("%02x ", data[i]); + printf("\n"); + } + int transferred_length = 0; + const int ret = libusb_bulk_transfer(dev_handle, endpoint, data, length, + &transferred_length, ESPUSBJTAG_TIMEOUT_MS); + + if (ret < 0) { + if (ret == -7 && is_timeout_fine) + return 0; + snprintf(mess, 128, "xfer: usb bulk write failed with error %d %s %s", ret, + libusb_error_name(ret), libusb_strerror(static_cast(ret))); + printError(mess); + return ret; + } + + if (is_read && _verbose) { + printf("xfer: read: "); + for (int i = 0; i < length; i++) + printf("%02x ", data[i]); + printf("\n"); + } + + return transferred_length; } // TODO // [ ] odd len // [ ] end (DR_SHIFT, IR_SHIFT) +// Note: as done for writeTMS, len and/or real_bit_len must be +// splitted in two loop level int esp_usb_jtag::writeTDI(const uint8_t *tx, uint8_t *rx, uint32_t len, bool end) { - int ret; - uint8_t tx_buf[OUT_EP_SZ]; - memset(rx, 0, len>>3); - #if 0 - // for debug force IDCODE 0x12345678 returned - if(len >= 4) memcpy(rx, "\x78\x56\x34\x12", 4); - return EXIT_SUCCESS; - #endif - uint32_t real_bit_len = len - (end ? 1 : 0); - // uint32_t kRealByteLen = (len + 7) / 8; - int transferred_length; + char mess[256]; + if (_verbose) { + snprintf(mess, 256, "writeTDI: start len: %d end: %d", len, end); + printSuccess(mess); + } + int ret; + const uint32_t kTdiLen = (len+7) >> 3; // TDI/RX len in byte + uint8_t tdi[kTdiLen]; // TDI buffer (required when tx is NULL) + uint8_t tx_buf[OUT_EP_SZ]; + const uint8_t tdo = !(rx == NULL); // only set cap/tdo when something to read + uint8_t *rx_ptr = NULL; + uint32_t xfer_len = 0; - if (len == 0) - return 0; + /* nothing to do ? */ + if (len == 0) + return 0; - cerr << "real_bit_len=0x" << real_bit_len << endl; + /* set rx to 0: to be removed when working */ + if (rx) { + memset(rx, 0, (len + 7) >> 3); + rx_ptr = rx; + } - // drain_in(); - uint8_t prev_high_nibble = CMD_FLUSH << 4; // for odd length 1st command is flush = nop - uint32_t tx_buffer_idx = 0; // reset - uint8_t is_high_nibble = 1 & ~real_bit_len; - // for even len: start with is_high_nibble = 1 - // for odd len: start with is_high_nibble = 0 - // 1st (high nibble) is flush = nop - // 2nd (low nibble) is data - // last byte in buf will have data in both nibbles, no flush - // exec order: high-nibble-first, low-nibble-second - cerr << "is high nibble=" << (int)is_high_nibble << endl; - int bits_in_tx_buf = 0; - for(int i = 0; i < (real_bit_len+7)>>3; i++) - cerr << " " << std::hex << (int)tx[i]; - cerr << endl; - cerr << "tdi_bits "; - for (uint32_t i = 0; i < real_bit_len; i++) - { - uint8_t tdi_bit = (tx[i>>3] >> (i&7)) & 1; // get i'th bit from rx - cerr << (int)tdi_bit; - uint8_t cmd = CMD_CLK(/*tdo*/1, /*tdi*/tdi_bit, /*tms*/0); // with TDO capture - if(is_high_nibble) - { // 1st (high nibble) = data - tx_buf[tx_buffer_idx] = prev_high_nibble = cmd << 4; - is_high_nibble = 0; - } - else // low nibble - { // 2nd (low nibble) = data, keep prev high nibble - tx_buf[tx_buffer_idx++] = prev_high_nibble | cmd; - is_high_nibble = 1; - } - bits_in_tx_buf++; - if (tx_buffer_idx >= sizeof(tx_buf) /*buf full*/ || i >= real_bit_len - 1 /*last*/) - { - cerr << endl << "writeTDI: write_ep len bytes=0x" << tx_buffer_idx << endl; - for(int j = 0; j < tx_buffer_idx; j++) - cerr << " " << std::hex << (int)tx_buf[j]; - cerr << endl; - ret = libusb_bulk_transfer(dev_handle, - /*endpoint*/ ESPUSBJTAG_WRITE_EP, - /*data*/ tx_buf, - /*length*/ tx_buffer_idx, - /*transferred length*/ &transferred_length, - /*timeout ms*/ ESPUSBJTAG_TIMEOUT_MS); - if (ret != 0) - { - cerr << "writeTDI: usb bulk write failed " << ret << endl; - return -EXIT_FAILURE; - } - cerr << "writeTDI write 0x" << tx_buffer_idx << " bytes" << endl; - flush(); // must flush before reading - // TODO support odd len for TDO - // currently only even len TDO works correctly - // for odd len first command sent is CMD_FUSH - // so TDI rx_buf will be missing 1 bit - uint16_t read_bit_len = tx_buffer_idx<<1; - // read_bit_len = bits_in_tx_buf; - uint16_t read_byte_len = (read_bit_len+7)>>3; - // cerr << "read_bit_len=" << (int)read_bit_len << " read_byte_len=" << (int)read_byte_len << endl; - // read_byte_len = 1; - int received_bytes = 0; - while(received_bytes < read_byte_len) - { - ret = libusb_bulk_transfer(dev_handle, - /*endpoint*/ ESPUSBJTAG_READ_EP, - /*data*/ &(rx[(i>>3)+received_bytes]), - /*length*/ read_byte_len-received_bytes, - /*transferred length*/ &transferred_length, - /*timeout ms*/ ESPUSBJTAG_TIMEOUT_MS); - if (ret != 0) - { - cerr << "writeTDI: usb bulk read failed " << ret << endl; - // return -EXIT_FAILURE; - break; - } - cerr << "writeTDI read" << endl; - if (read_byte_len != transferred_length) - { - cerr << "writeTDI: usb bulk read expected=" << read_byte_len << " received=" << transferred_length << endl; - // return -EXIT_FAILURE; - break; - } - received_bytes += transferred_length; - } - tx_buffer_idx = 0; // reset - bits_in_tx_buf = 0; - } - } + /* Copy RX or fill the buffer with TDI current level */ + if (tx) + memcpy(tdi, tx, kTdiLen); + else + memset(tdi, _tdi ? 0xff : 0x00, kTdiLen); - #if 0 - if(end) - { - // TODO support end (DR_SHIFT, IR_SHIFT) - } - #endif - return EXIT_SUCCESS; + if (_verbose) { + snprintf(mess, 256, "len=0x%08x\n", len); + printInfo(mess); + } + + // drain_in(); + uint32_t tx_buffer_idx = 0; // reset + uint8_t is_high_nibble = 1 & ~len; + + // for even len: start with is_high_nibble = 1 + // for odd len: start with is_high_nibble = 0 + // 1st (high nibble) is flush = nop + // 2nd (low nibble) is data + // last byte in buf will have data in both nibbles, no flush + // exec order: high-nibble-first, low-nibble-second + if (_verbose) { + cerr << "is high nibble=" << (int)is_high_nibble << endl; + //int bits_in_tx_buf = 0; + for(uint32_t i = 0; i < (len + 7) >> 3; i++) + cerr << " " << std::hex << (int)tdi[i]; + cerr << endl; + cerr << "tdi_bits "; + } + + for (uint32_t pos = 0; pos < len; pos += xfer_len) { + // Compute number of bits to write + const uint32_t remaining_bits = len - pos; + // select before the full buffer or remaining bits + if (remaining_bits < (OUT_EP_SZ * 2)) + xfer_len = remaining_bits; + else + xfer_len = OUT_EP_SZ * 2; + + uint8_t prev_high_nibble = CMD_FLUSH << 4; // for odd length 1st command is flush = nop + tx_buffer_idx = 0; // reset + uint8_t is_high_nibble = 1 & ~xfer_len; + // for even len: start with is_high_nibble = 1 + // for odd len: start with is_high_nibble = 0 + // 1st (high nibble) is flush = nop + // 2nd (low nibble) is data + // last byte in buf will have data in both nibbles, no flush + // exec order: high-nibble-first, low-nibble-second + + for (uint32_t i = 0; i < xfer_len; i++) { + uint32_t curr_pos = pos + i; + _tdi = (tdi[curr_pos >> 3] >> (curr_pos & 7)) & 1; // get i'th bit from rx + if (_verbose) + cerr << (int)_tdi; + if (end && curr_pos == len - 1) + _tms = 1; + const uint8_t cmd = CMD_CLK(tdo, _tdi, _tms); // with TDO capture + if(is_high_nibble) { // 1st (high nibble) = data + tx_buf[tx_buffer_idx] = prev_high_nibble = cmd << 4; + } else { // low nibble + // 2nd (low nibble) = data, keep prev high nibble + tx_buf[tx_buffer_idx++] = prev_high_nibble | cmd; + } + is_high_nibble ^= 1; + } + + /* Flush current buffer */ + if (_verbose) { + printf("\nwriteTDI: write_ep len bytes=0x%04x\n", tx_buffer_idx); + for(uint32_t j = 0; j < tx_buffer_idx; j++) + printf(" %02x", tx_buf[j]); + printf("\n"); + printf("AA\n"); + } + ret = xfer(tx_buf, NULL, tx_buffer_idx); + if (_verbose) + printf("BB\n"); + if (ret < 0) { + printError("writeTDI: usb bulk write failed " + std::to_string(ret)); + return -EXIT_FAILURE; + } + if (_verbose) + cerr << "writeTDI write 0x" << tx_buffer_idx << " bytes" << endl; + if (rx) { + flush(); // must flush before reading + // TODO support odd len for TDO + // currently only even len TDO works correctly + // for odd len first command sent is CMD_FUSH + // so TDI rx_buf will be missing 1 bit + uint16_t read_bit_len = tx_buffer_idx << 1; + uint16_t read_byte_len = (read_bit_len + 7) >> 3; + for (int rx_bytes = 0; rx_bytes < read_byte_len; rx_bytes += ret) { + int nb_try = 0; // try more than one time, sometime flush is not immediate + do { + ret = xfer(NULL, rx_ptr, read_byte_len - rx_bytes); + if (ret < 0) { + printError("writeTDI: read failed"); + return -EXIT_FAILURE; + } + nb_try++; + } while (nb_try < 3 && ret == 0); + if (_verbose) + cerr << "writeTDI read " << std::to_string(ret) << endl; + if (read_byte_len != ret) { + snprintf(mess, 256, "writeTDI: usb bulk read expected=%d received=%d", read_byte_len, ret); + printError(mess); + break; + } + rx_ptr += ret; + } + } + } + + if (_verbose) + printSuccess("WriteTDI: end"); + + return EXIT_SUCCESS; } diff --git a/src/esp_usb_jtag.hpp b/src/esp_usb_jtag.hpp index 6744af7..8d1da38 100644 --- a/src/esp_usb_jtag.hpp +++ b/src/esp_usb_jtag.hpp @@ -18,46 +18,49 @@ */ class esp_usb_jtag : public JtagInterface { - public: - esp_usb_jtag(uint32_t clkHZ, int8_t verbose, int vid, int pid); - virtual ~esp_usb_jtag(); + public: + esp_usb_jtag(uint32_t clkHZ, int8_t verbose, int vid, int pid); + virtual ~esp_usb_jtag(); - int setClkFreq(uint32_t clkHZ) override; + int setClkFreq(uint32_t clkHZ) override; - /* TMS */ - int writeTMS(const uint8_t *tms, uint32_t len, bool flush_buffer, const uint8_t tdi = 1) override; - /* TDI */ - int writeTDI(const uint8_t *tx, uint8_t *rx, uint32_t len, bool end) override; - /* clk */ - int toggleClk(uint8_t tms, uint8_t tdo, uint32_t clk_len) override; + /* TMS */ + int writeTMS(const uint8_t *tms, uint32_t len, bool flush_buffer, const uint8_t tdi = 1) override; + /* TDI */ + int writeTDI(const uint8_t *tx, uint8_t *rx, uint32_t len, bool end) override; + /* clk */ + int toggleClk(uint8_t tms, uint8_t tdo, uint32_t clk_len) override; - /*! - * \brief return internal buffer size (in byte). - * \return _buffer_size divided by 2 (two byte for clk) and divided by 8 (one - * state == one byte) - */ - int get_buffer_size() override { return 0;} + /*! + * \brief return internal buffer size (in byte). + * \return _buffer_size divided by 2 (two byte for clk) and divided by 8 (one + * state == one byte) + */ + int get_buffer_size() override { return 0;} - bool isFull() override { return false;} + bool isFull() override { return false;} - int flush() override; + int flush() override; - private: - int8_t _verbose; + private: + int xfer(const uint8_t *tx, uint8_t *rx, uint16_t length, + bool is_timeout_fine=false); - // int sendBitBang(uint8_t mask, uint8_t val, uint8_t *read, bool last); - bool getVersion(); - - void drain_in(); - int setio(int srst, int tms, int tdi, int tck); - int gettdo(); + int8_t _verbose; - libusb_device_handle *dev_handle; - libusb_context *usb_ctx; - uint8_t _tdi; - uint8_t _tms; - uint8_t _version; - uint32_t _base_speed_khz; - uint8_t _div_min, _div_max; + // int sendBitBang(uint8_t mask, uint8_t val, uint8_t *read, bool last); + bool getVersion(); + + void drain_in(bool is_timeout_fine=false); + int setio(int srst, int tms, int tdi, int tck); + int gettdo(); + + libusb_device_handle *dev_handle; + libusb_context *usb_ctx; + uint8_t _tdi; + uint8_t _tms; + uint8_t _version; + uint32_t _base_speed_khz; + uint8_t _div_min, _div_max; }; #endif // SRC_ESPUSBJTAG_HPP_ From 6c0c38d0ccd05cf4fec0609fcebae79312de7046 Mon Sep 17 00:00:00 2001 From: Gwenhael Goavec-Merou Date: Sun, 20 Apr 2025 12:47:28 +0200 Subject: [PATCH 3/4] esp_usb_jtag: added reference to esp32s3-jtag repository --- src/esp_usb_jtag.cpp | 4 ++++ src/esp_usb_jtag.hpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/esp_usb_jtag.cpp b/src/esp_usb_jtag.cpp index 14769df..4c936d0 100644 --- a/src/esp_usb_jtag.cpp +++ b/src/esp_usb_jtag.cpp @@ -3,6 +3,10 @@ * Copyright (C) 2024 EMARD */ +/* To prepare the cable see: + * https://github.com/emard/esp32s3-jtag + */ + /* Holy Crap, it's protocol documentation, and it's even vendor-provided! diff --git a/src/esp_usb_jtag.hpp b/src/esp_usb_jtag.hpp index 8d1da38..5197913 100644 --- a/src/esp_usb_jtag.hpp +++ b/src/esp_usb_jtag.hpp @@ -3,6 +3,10 @@ * Copyright (C) 2024 EMARD */ +/* To prepare the cable see: + * https://github.com/emard/esp32s3-jtag + */ + #ifndef SRC_ESPUSBJTAG_HPP_ #define SRC_ESPUSBJTAG_HPP_ From 77323000eda071317ffdcb1068c4117b9ae1fa7f Mon Sep 17 00:00:00 2001 From: Gwenhael Goavec-Merou Date: Sun, 20 Apr 2025 12:47:50 +0200 Subject: [PATCH 4/4] 99-openfpgaloader.rules: added rule for ESP32S3 --- 99-openfpgaloader.rules | 3 +++ 1 file changed, 3 insertions(+) diff --git a/99-openfpgaloader.rules b/99-openfpgaloader.rules index a3977a1..3f0668d 100644 --- a/99-openfpgaloader.rules +++ b/99-openfpgaloader.rules @@ -60,4 +60,7 @@ ATTRS{idVendor}=="1209", ATTRS{idProduct}=="3442", MODE="664", GROUP="plugdev", # QinHeng Electronics USB To UART+JTAG (ch347) ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="55dd", MODE="664", GROUP="plugdev", TAG+="uaccess" +# ESP32-S3 (usb-jtag bridge) +ATTRS{idVendor}=="303a", ATTRS{idProduct}=="1001", MODE="664", GROUP="plugdev", TAG+="uaccess" + LABEL="openfpgaloader_rules_end"