From 0fc8ba10a8e73439e738562da86fcee01b157e8e Mon Sep 17 00:00:00 2001 From: Alexey Starikovskiy Date: Sat, 13 May 2023 23:00:23 +0300 Subject: [PATCH] Add WCH CH347T(mode #3) JTAG cable Signed-off-by: Gwenhael Goavec-Merou --- 99-openfpgaloader.rules | 3 + CMakeLists.txt | 2 + doc/cable.yml | 14 ++ src/cable.hpp | 2 + src/ch347jtag.cpp | 388 ++++++++++++++++++++++++++++++++++++++++ src/ch347jtag.hpp | 42 +++++ src/jtag.cpp | 5 + 7 files changed, 456 insertions(+) create mode 100644 src/ch347jtag.cpp create mode 100644 src/ch347jtag.hpp diff --git a/99-openfpgaloader.rules b/99-openfpgaloader.rules index be2fcbb..d3ce156 100644 --- a/99-openfpgaloader.rules +++ b/99-openfpgaloader.rules @@ -53,4 +53,7 @@ ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="6146", MODE="664", GROUP="plugdev", # orbtrace-mini dfu ATTRS{idVendor}=="1209", ATTRS{idProduct}=="3442", MODE="664", GROUP="plugdev", TAG+="uaccess" +# QinHeng Electronics USB To UART+JTAG (ch347) +ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="55dd", MODE="664", GROUP="plugdev", TAG+="uaccess" + LABEL="openfpgaloader_rules_end" diff --git a/CMakeLists.txt b/CMakeLists.txt index c692d1e..3a1514d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,6 +96,7 @@ set(OPENFPGALOADER_SOURCE src/dfu.cpp src/dfuFileParser.cpp src/dirtyJtag.cpp + src/ch347jtag.cpp src/efinix.cpp src/efinixHexParser.cpp src/fx2_ll.cpp @@ -146,6 +147,7 @@ set(OPENFPGALOADER_HEADERS src/dfu.hpp src/dfuFileParser.hpp src/dirtyJtag.hpp + src/ch347jtag.hpp src/efinix.hpp src/efinixHexParser.hpp src/fx2_ll.hpp diff --git a/doc/cable.yml b/doc/cable.yml index 7452162..3f89331 100644 --- a/doc/cable.yml +++ b/doc/cable.yml @@ -26,7 +26,15 @@ bus_blaster_b: URL: http://dangerousprototypes.com/docs/Bus_Blaster +ch347_jtag: + + - Name: ch347 JTAG adapter + Description: QinHeng Electronics USB To UART+JTAG (mode 3) + URL: https://www.wch-ic.com/products/CH347.html + + ch552_jtag: + - Name: ch552 JTAG adapter Description: Tang Nano USB-JTAG interface. FT2232C clone firmware for CH552 microcontroler URL: https://github.com/diodep/ch55x_jtag @@ -276,3 +284,9 @@ jetson-nano-gpio: - Name: Bitbang GPIO Description: Bitbang GPIO pins on Jetson Nano Linux host. Use /dev/mem to have a faster clock. URL: https://github.com/jwatte/jetson-gpio-example + +ch347: + + - Name: CH347 + Description: CH347 is a USB HS bus converter with UART, I2C, SPI and JTAG interfaces + URL: https://github.com/wuxx/USB-HS-Bridge diff --git a/src/cable.hpp b/src/cable.hpp index 7dcba63..abc4a70 100644 --- a/src/cable.hpp +++ b/src/cable.hpp @@ -27,6 +27,7 @@ enum communication_type { MODE_LIBGPIOD_BITBANG, /*! Bitbang gpio pins */ MODE_JETSONNANO_BITBANG, /*! Bitbang gpio pins */ MODE_REMOTEBITBANG, /*! Remote Bitbang mode */ + MODE_CH347, /*! CH347 JTAG mode */ }; /*! @@ -85,6 +86,7 @@ static std::map cable_list = { {"bus_blaster", FTDI_SER(0x0403, 0x6010, FTDI_INTF_A, 0x08, 0x1B, 0x08, 0x0B)}, {"bus_blaster_b", FTDI_SER(0x0403, 0x6010, FTDI_INTF_B, 0x08, 0x0B, 0x08, 0x0B)}, {"ch552_jtag", FTDI_SER(0x0403, 0x6010, FTDI_INTF_A, 0x08, 0x0B, 0x08, 0x0B)}, + {"ch347_jtag", CABLE_DEF(MODE_CH347, 0x1a86, 0x55dd )}, {"cmsisdap", CMSIS_CL(0x0d28, 0x0204 )}, {"gatemate_pgm", FTDI_SER(0x0403, 0x6014, FTDI_INTF_A, 0x10, 0x9B, 0x14, 0x17)}, {"gatemate_evb_jtag", FTDI_SER(0x0403, 0x6010, FTDI_INTF_A, 0x10, 0x1B, 0x00, 0x01)}, diff --git a/src/ch347jtag.cpp b/src/ch347jtag.cpp new file mode 100644 index 0000000..7d25968 --- /dev/null +++ b/src/ch347jtag.cpp @@ -0,0 +1,388 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2023 Alexey Starikovskiy + */ + +#define _DEFAULT_SOURCE + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "ch347jtag.hpp" +#include "display.hpp" + +using namespace std; + +#define CH347JTAG_VID 0x1a86 +#define CH347JTAG_PID 0x55dd + +#define CH347JTAG_INTF 2 +#define CH347JTAG_WRITE_EP 0x06 +#define CH347JTAG_READ_EP 0x86 + +#define CH347JTAG_TIMEOUT 2000 + +enum CH347JtagCmd { + CMD_BYTES_WO = 0xd3, + CMD_BYTES_WR = 0xd4, + CMD_BITS_WO = 0xd1, + CMD_BITS_WR = 0xd2, + CMD_CLK = 0xd0, +}; + +enum CH347JtagSig { + SIG_TCK = 0b1, + SIG_TMS = 0b10, + SIG_TDI = 0b10000, +}; +static void sync_cb(libusb_transfer *transfer) { + int *complete = (int *)transfer->user_data; + *complete = true; +} + +int CH347Jtag::usb_xfer(unsigned wlen, unsigned rlen, unsigned *ract) { + wcomplete = 0; + if (_verbose) { + fprintf(stderr, "obuf[%d] = {", wlen); + for (unsigned i = 0; i < wlen; ++i) { + fprintf(stderr, "%02x ", obuf[i]); + } + fprintf(stderr, "}\n\n"); + } + libusb_fill_bulk_transfer(wtrans, dev_handle, CH347JTAG_WRITE_EP, obuf, + wlen, sync_cb, &wcomplete, CH347JTAG_TIMEOUT); + int r = libusb_submit_transfer(wtrans); + if (r < 0) + return r; + if (rlen) { + rcomplete = 0; + libusb_fill_bulk_transfer(rtrans, dev_handle, CH347JTAG_READ_EP, ibuf, + rlen, sync_cb, &rcomplete, CH347JTAG_TIMEOUT); + r = libusb_submit_transfer(rtrans); + if (r < 0) + return r; + } else { + rcomplete = 1; + } + while (!wcomplete) { + r = libusb_handle_events_completed(usb_ctx, &wcomplete); + if (r < 0) { + if (r != LIBUSB_ERROR_INTERRUPTED) { + libusb_cancel_transfer(wtrans); + if (rlen) libusb_cancel_transfer(rtrans); + } + continue; + } + if (!wtrans->dev_handle) { + wtrans->status = LIBUSB_TRANSFER_NO_DEVICE; + wcomplete = 1; + } + } + unsigned rdone = 0; +wait_rcompletion: + while (!rcomplete) { + r = libusb_handle_events_completed(usb_ctx, &rcomplete); + if (r < 0) { + if (r != LIBUSB_ERROR_INTERRUPTED) + libusb_cancel_transfer(rtrans); + continue; + } + if (!rtrans->dev_handle) { + rtrans->status = LIBUSB_TRANSFER_NO_DEVICE; + rcomplete = 1; + } + } + if (wtrans->status != LIBUSB_TRANSFER_COMPLETED) { + return LIBUSB_ERROR_IO; + } + if (rlen) { + rdone += rtrans->actual_length; + if (rtrans->status != LIBUSB_TRANSFER_COMPLETED) { + return LIBUSB_ERROR_IO; + } + if (rlen > rdone) { + rcomplete = 0; + libusb_fill_bulk_transfer(rtrans, dev_handle, CH347JTAG_READ_EP, + &ibuf[rdone], rlen - rdone, sync_cb, &rcomplete, + CH347JTAG_TIMEOUT); + r = libusb_submit_transfer(rtrans); + if (r < 0) + return r; + goto wait_rcompletion; + } + if (ract) *ract = rdone; + if (_verbose) { + fprintf(stderr, "ibuf[%d] = {", rdone); + for (unsigned i = 0; i < rdone; ++i) { + fprintf(stderr, "%02x ", ibuf[i]); + } + fprintf(stderr, "}\n\n"); + } + } + return 0; +} + +int CH347Jtag::setClk(const uint8_t &factor) { + memset(obuf, 0, 16); + obuf[0] = CMD_CLK; + obuf[1] = 6; + obuf[4] = factor; + unsigned actual = 0; + int rv = usb_xfer(9, 4, &actual); + if (rv || actual != 4) + return -1; + if (ibuf[0] != 0xd0 || ibuf[3] != 0) + return -1; + return 0; +} + +CH347Jtag::CH347Jtag(uint32_t clkHZ, uint8_t verbose): + _verbose(verbose), dev_handle(NULL), usb_ctx(NULL) +{ + int actual_length = 0; + struct libusb_device_descriptor desc; + struct libusb_device *dev; + int rv; + if (libusb_init(&usb_ctx) < 0) { + printError("libusb init failed"); + goto err_exit; + } + dev_handle = libusb_open_device_with_vid_pid(usb_ctx, CH347JTAG_VID, + CH347JTAG_PID); + if (!dev_handle) { + printError("fails to open device"); + goto usb_exit; + } + dev = libusb_get_device(dev_handle); + if (!dev) { + printError("Couldnt get bus number and address of device"); + goto usb_exit; + } + + rv = libusb_get_device_descriptor(dev, &desc); + if (rv < 0) { + printError("failed to get device descriptor"); + goto usb_exit; + } + + if (desc.bcdDevice < 0x241) { + printWarn("Old version of the chip, JTAG might not work"); + } + + if (libusb_set_auto_detach_kernel_driver(dev_handle, true)) { + printError("libusb error wrile setting auto-detach of kernel driver"); + goto usb_exit; + } + if (libusb_claim_interface(dev_handle, CH347JTAG_INTF)) { + printError("libusb error while claiming CH347JTAG interface"); + goto usb_close; + } + rtrans = libusb_alloc_transfer(0); + wtrans = libusb_alloc_transfer(0); + if (!rtrans || !wtrans) { + printError("libusb failed to alloc transfers"); + goto usb_release; + } + libusb_bulk_transfer(dev_handle, CH347JTAG_READ_EP, ibuf, 512, + &actual_length, CH347JTAG_TIMEOUT); + setClkFreq(clkHZ); + return; +usb_release: + libusb_release_interface(dev_handle, CH347JTAG_INTF); +usb_close: + libusb_close(dev_handle); +usb_exit: + libusb_exit(usb_ctx); +err_exit: + throw std::exception(); +} + +CH347Jtag::~CH347Jtag() +{ + if (rtrans) libusb_free_transfer(rtrans); + if (wtrans) libusb_free_transfer(wtrans); + + if (dev_handle) { + libusb_release_interface(dev_handle, CH347JTAG_INTF); + libusb_close(dev_handle); + dev_handle = 0; + } + if (usb_ctx) { + libusb_exit(usb_ctx); + usb_ctx = 0; + } +} + +int CH347Jtag::setClkFreq(uint32_t clkHZ) +{ + unsigned i = 0, sl = 2000000; + if (clkHZ <= 2000000) { + _clkHZ = 2000000; + i = 0; + } else { + for (; i < 5; ++i, sl *= 2) { + if (clkHZ < sl) break; + _clkHZ = sl; + } + i--; + } + if (setClk(i)) { + printError("failed to set clock rate"); + return 0; + } + char mess[256]; + snprintf(mess, 256, "JTAG TCK frequency set to %d MHz\n\n", _clkHZ/1000000); + printInfo(mess); + return _clkHZ; +} + +int CH347Jtag::writeTMS(uint8_t *tms, uint32_t len, bool flush_buffer) +{ + (void) flush_buffer; + + uint8_t *ptr = obuf; + for (uint32_t i = 0; i < len; ++i) { + if (ptr == obuf) { + *ptr++ = CMD_BITS_WO; + ptr += 2; // leave place for length; + } + uint8_t x = /*SIG_TDI |*/ ((tms[i >> 3] & (1 << (i & 7))) ? SIG_TMS : 0); + *ptr++ = x; + *ptr++ = x | SIG_TCK; + unsigned wlen = ptr - obuf; + if (wlen > sizeof(obuf) - 3 || i == len - 1) { + *ptr++ = x; // clear TCK + ++wlen; + obuf[1] = wlen - 3; + obuf[2] = (wlen - 3) >> 8; + int ret = usb_xfer(wlen, 0, 0); + if (ret < 0) { + cerr << "writeTMS: usb bulk write failed: " << libusb_strerror(ret) << endl; + return -EXIT_FAILURE; + } + ptr = obuf; + } + } + return len; +} + +int CH347Jtag::toggleClk(uint8_t tms, uint8_t tdi, uint32_t len) +{ + uint8_t bits = 0; + if (tms) bits |= SIG_TMS; + if (tdi) bits |= SIG_TDI; + + uint8_t *ptr = obuf; + for (uint32_t i = 0; i < len; ++i) { + if (ptr == obuf) { + *ptr++ = CMD_BITS_WO; + ptr += 2; // leave place for length; + } + *ptr++ = bits; + *ptr++ = bits | SIG_TCK; + unsigned wlen = ptr - obuf; + if (wlen > sizeof(obuf) - 3 || i == len - 1) { + *ptr++ = bits; // clear TCK + ++wlen; + obuf[1] = wlen - 3; + obuf[2] = (wlen - 3) >> 8; + int ret = usb_xfer(wlen, 0, 0); + if (ret < 0) { + cerr << "writeCLK: usb bulk write failed: " << libusb_strerror(ret) << endl; + return -EXIT_FAILURE; + } + ptr = obuf; + } + } + return EXIT_SUCCESS; +} + +int CH347Jtag::writeTDI(uint8_t *tx, uint8_t *rx, uint32_t len, bool end) +{ + if (!tx || !len) + return 0; + unsigned bytes = (len - ((end)?1:0)) / 8; + unsigned bits = len - bytes * 8; + uint8_t *rptr = rx; + uint8_t *tptr = tx; + uint8_t *txend = &tx[bytes]; + uint8_t cmd = (rx) ? CMD_BYTES_WR : CMD_BYTES_WO; + while (tptr < txend) { + unsigned avail = sizeof(obuf) - 3; + unsigned chunk = (txend - tptr < avail)? txend - tptr: avail; + memcpy(&obuf[3], tptr, chunk); + tptr += chunk; + // write header + obuf[0] = cmd; + obuf[1] = chunk; + obuf[2] = chunk >> 8; + unsigned actual_length = 0; + int ret = usb_xfer(chunk + 3, (rx) ? chunk + 3 : 0, &actual_length); + if (ret < 0) { + cerr << "writeTDI: usb bulk read failed: " << libusb_strerror(ret) << endl; + return -EXIT_FAILURE; + } + if (!rx) + continue; + unsigned size = ibuf[1] + ibuf[2] * 0x100; + if (ibuf[0] != CMD_BYTES_WR || actual_length - 3 != size) { + cerr << "writeTDI: invalid read data: " << ret << endl; + return -EXIT_FAILURE; + } + memcpy(rptr, &ibuf[3], size); + rptr += size; + } + unsigned actual_length; + if (bits == 0) + return EXIT_SUCCESS; + cmd = (rx) ? CMD_BITS_WR : CMD_BITS_WO; + uint8_t *ptr = &obuf[3]; + uint8_t x = 0; + uint8_t *bptr = &tx[bytes]; + for (unsigned i = 0; i < bits; ++i) { + uint8_t txb = bptr[i >> 3]; + uint8_t _tdi = (txb & (1 << (i & 7))) ? SIG_TDI : 0; + x = _tdi; + if (end && i == bits - 1) { + x |= SIG_TMS; + } + *ptr++ = x; + *ptr++ = x | SIG_TCK; + } + *ptr++ = x & ~SIG_TCK; + unsigned wlen = ptr - obuf; + obuf[0] = cmd; + obuf[1] = wlen - 3; + obuf[2] = (wlen - 3) >> 8; + int ret = usb_xfer(wlen, (rx) ? (bits + 3) : 0, &actual_length); + + if (ret < 0) { + cerr << "writeTDI: usb bulk read failed: " << libusb_strerror(ret) << endl; + return -EXIT_FAILURE; + } + if (!rx) + return EXIT_SUCCESS; + + unsigned size = ibuf[1] + ibuf[2] * 0x100; + + if (ibuf[0] != CMD_BITS_WR || actual_length - 3 != size) { + cerr << "writeTDI: invalid read data: " << endl; + return -EXIT_FAILURE; + } + for (unsigned i = 0; i < size / 16; ++i) { + uint8_t b = 0; + uint8_t *xb = &ibuf[3 + i * 16 + 1]; + for (unsigned j = 0; j < 8; ++j) + b |= (xb[j] & 1) << j; + *rptr++ = b; + } + return EXIT_SUCCESS; +} diff --git a/src/ch347jtag.hpp b/src/ch347jtag.hpp new file mode 100644 index 0000000..86d8307 --- /dev/null +++ b/src/ch347jtag.hpp @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2023 Alexey Starikovskiy + */ +#pragma once + +#include + +#include "jtagInterface.hpp" + +class CH347Jtag : public JtagInterface { + public: + CH347Jtag(uint32_t clkHZ, uint8_t verbose); + virtual ~CH347Jtag(); + + int setClkFreq(uint32_t clkHZ) override; + + /* TMS */ + int writeTMS(uint8_t *tms, uint32_t len, bool flush_buffer) override; + /* TDI */ + int writeTDI(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; + + int get_buffer_size() override { return 0;} + + bool isFull() override { return false;} + + int flush() override {return 0;} + + private: + uint8_t _verbose; + int setClk(const uint8_t &factor); + + libusb_device_handle *dev_handle; + libusb_context *usb_ctx; + struct libusb_transfer *wtrans, *rtrans; + int rcomplete, wcomplete; + uint8_t ibuf[512]; + uint8_t obuf[512]; + int usb_xfer(unsigned wlen, unsigned rlen, unsigned *actual); +}; diff --git a/src/jtag.cpp b/src/jtag.cpp index 7847def..2a71b4e 100644 --- a/src/jtag.cpp +++ b/src/jtag.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -28,6 +29,7 @@ #include "cmsisDAP.hpp" #endif #include "dirtyJtag.hpp" +#include "ch347jtag.hpp" #include "part.hpp" #ifdef ENABLE_REMOTEBITBANG #include "remoteBitbang_client.hpp" @@ -111,6 +113,9 @@ void Jtag::init_internal(const cable_t &cable, const string &dev, const string & case MODE_CH552_JTAG: _jtag = new CH552_jtag(cable, dev, serial, clkHZ, _verbose); break; + case MODE_CH347: + _jtag = new CH347Jtag(clkHZ, _verbose); + break; case MODE_DIRTYJTAG: _jtag = new DirtyJtag(clkHZ, _verbose); break;