Merge pull request #540 from trabucayre/esp_usb_jtag

Esp usb jtag Cable support
This commit is contained in:
Gwenhael Goavec-Merou 2025-04-20 16:51:06 +02:00 committed by GitHub
commit e9b8dc5e4c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 866 additions and 0 deletions

View File

@ -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"

View File

@ -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(

View File

@ -237,6 +237,7 @@ static std::map <std::string, target_board_t> 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),

View File

@ -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 <std::string, cable_t> 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)},

784
src/esp_usb_jtag.cpp Normal file
View File

@ -0,0 +1,784 @@
// SPDX-License-Identifier: Apache-2.0
/*
* 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!
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 <libusb.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <iostream>
#include <map>
#include <vector>
#include <string>
#include <cassert>
#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<<x)
#define CMD_CLK(cap, tdi, tms) (((1&cap)<<2) | ((1&tms)<<1) | (1&tdi))
#define CMD_RST(srst) (0x8 | (1&srst))
#define CMD_FLUSH 0xA
#define CMD_RSVD 0xB
#define CMD_REP(r) (0xC + ((r) & 3))
/* The internal repeats register is 10 bits, which means we can have 5 repeat commands in a
*row at max. This translates to ('b1111111111+1=)1024 reps max. */
#define CMD_REP_MAX_REPS 1024
/* Currently we only support one USB device. */
// #define USB_CONFIGURATION 0
/* Buffer size; is equal to the endpoint size. In bytes
* TODO for future adapters: read from device configuration? */
#define OUT_EP_SZ 64
/* Out data can be buffered for longer without issues (as long as the in buffer does not overflow),
* so we'll use an out buffer that is much larger than the out ep size. */
#define OUT_BUF_SZ (OUT_EP_SZ * 32)
/* The in buffer cannot be larger than the device can offer, though. */
#define IN_BUF_SZ 64
/* Because a series of out commands can lead to a multitude of IN_BUF_SZ-sized in packets
*to be read, we have multiple buffers to store those before the bitq interface reads them out. */
#define IN_BUF_CT 8
/*
* comment from libusb:
* As per the USB 3.0 specs, the current maximum limit for the depth is 7.
*/
#define MAX_USB_PORTS 7
/* Private data */
struct esp_usb_jtag_s {
struct libusb_device_handle *usb_device;
uint32_t base_speed_khz;
uint16_t div_min;
uint16_t div_max;
uint8_t out_buf[OUT_BUF_SZ];
unsigned int out_buf_pos_nibbles; /* write position in out_buf */
uint8_t in_buf[IN_BUF_CT][IN_BUF_SZ];
unsigned int in_buf_size_bits[IN_BUF_CT]; /* size in bits of the data stored in an in_buf */
unsigned int cur_in_buf_rd, cur_in_buf_wr; /* read/write index */
unsigned int in_buf_pos_bits; /* which bit in the in buf needs to be returned to bitq next */
unsigned int read_ep;
unsigned int write_ep;
unsigned int prev_cmd; /* previous command, stored here for RLEing. */
int prev_cmd_repct; /* Amount of repetitions of that command we have seen until now */
/* This is the total number of in bits we need to read, including in unsent commands */
unsigned int pending_in_bits;
// FILE *logfile; /* If non-NULL, we log communication traces here. */
unsigned int hw_in_fifo_len;
char *serial[256 + 1]; /* device serial */
// struct bitq_interface bitq_interface;
};
/* For now, we only use one static private struct. Technically, we can re-work this, but I don't think
* 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 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 */
/* end copy from openocd */
esp_usb_jtag::esp_usb_jtag(uint32_t clkHZ, int8_t verbose, int vid = ESPUSBJTAG_VID, int pid = ESPUSBJTAG_PID):
_verbose(verbose > 1),
dev_handle(NULL), usb_ctx(NULL), _tdi(0), _tms(0)
{
int ret;
if (libusb_init(&usb_ctx) < 0) {
cerr << "libusb init failed" << endl;
throw std::exception();
}
dev_handle = libusb_open_device_with_vid_pid(usb_ctx,
ESPUSBJTAG_VID, ESPUSBJTAG_PID);
if (!dev_handle) {
cerr << "fails to open esp_usb_jtag device vid:pid 0x" << std::hex << vid << ":0x" << std::hex << endl;
libusb_exit(usb_ctx);
throw std::exception();
}
ret = libusb_claim_interface(dev_handle, ESPUSBJTAG_INTF);
if (ret) {
cerr << "libusb error while claiming esp_usb_jtag interface of device vid:pid 0x" << std::hex << vid << ":0x" << std::hex << pid << endl;
libusb_close(dev_handle);
libusb_exit(usb_ctx);
throw std::exception();
}
_version = 0;
if (!getVersion())
throw std::runtime_error("Fail to get version");
if (setClkFreq(clkHZ) < 0) {
cerr << "Fail to set frequency" << endl;
throw std::exception();
}
}
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)
libusb_exit(usb_ctx);
}
bool esp_usb_jtag::getVersion()
{
/* 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. */
uint8_t jtag_caps_desc[JTAG_PROTO_CAPS_DATA_LEN];
int jtag_caps_read_len = libusb_control_transfer(dev_handle,
/*type*/ LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_STANDARD | LIBUSB_RECIPIENT_DEVICE,
/*brequest*/ LIBUSB_REQUEST_GET_DESCRIPTOR,
/*wvalue*/ esp_usb_jtag_caps,
/*interface*/ 0,
/*data*/ (unsigned char *)jtag_caps_desc,
/*length*/ JTAG_PROTO_CAPS_DATA_LEN,
/*timeout ms*/ ESPUSBJTAG_TIMEOUT_MS);
if (jtag_caps_read_len <= 0) {
cerr << "esp_usb_jtag: could not retrieve jtag_caps descriptor! len=" << jtag_caps_read_len << endl;
// goto out;
}
for(int i = 0; i < jtag_caps_read_len; i++)
cerr << " 0x" << std::hex << (int)(jtag_caps_desc[i]);
cerr << endl;
_base_speed_khz = UINT32_MAX;
_div_min = 1;
_div_max = 1;
int p = esp_usb_jtag_caps ==
VEND_DESCR_BUILTIN_JTAG_CAPS ? JTAG_BUILTIN_DESCR_START_OFF : JTAG_EUB_DESCR_START_OFF;
if (p + sizeof(struct jtag_proto_caps_hdr) > (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 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;
}
/* 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);
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()
{
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(bool is_timeout_fine)
{
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<libusb_error>(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)
{
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;
/* nothing to do ? */
if (len == 0)
return 0;
/* set rx to 0: to be removed when working */
if (rx) {
memset(rx, 0, (len + 7) >> 3);
rx_ptr = rx;
}
/* Copy RX or fill the buffer with TDI current level */
if (tx)
memcpy(tdi, tx, kTdiLen);
else
memset(tdi, _tdi ? 0xff : 0x00, kTdiLen);
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;
}

70
src/esp_usb_jtag.hpp Normal file
View File

@ -0,0 +1,70 @@
// SPDX-License-Identifier: Apache-2.0
/*
* Copyright (C) 2024 EMARD
*/
/* To prepare the cable see:
* https://github.com/emard/esp32s3-jtag
*/
#ifndef SRC_ESPUSBJTAG_HPP_
#define SRC_ESPUSBJTAG_HPP_
#include <libusb.h>
#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:
int xfer(const uint8_t *tx, uint8_t *rx, uint16_t length,
bool is_timeout_fine=false);
int8_t _verbose;
// 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_

View File

@ -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;