diff --git a/CMakeLists.txt b/CMakeLists.txt index f77f98c..f2e9f1e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -210,6 +210,15 @@ if (ENABLE_CMSISDAP) endif() endif(ENABLE_CMSISDAP) +if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") + add_definitions(-DENABLE_XVC_CLIENT=1) + target_sources(openFPGALoader PRIVATE src/xvc_client.cpp) + list (APPEND OPENFPGALOADER_HEADERS src/xvc_client.hpp) + message("Xilinx Virtual Server (client side) support enabled") +else() + message("Xilinx Virtual Server (client side) support disabled") +endif() + if (ZLIB_FOUND) include_directories(${ZLIB_INCLUDE_DIRS}) target_link_libraries(openFPGALoader ${ZLIB_LIBRARIES}) diff --git a/doc/cable.yml b/doc/cable.yml index 2b3a16c..0b3a8f3 100644 --- a/doc/cable.yml +++ b/doc/cable.yml @@ -222,3 +222,10 @@ usb-blasterII: - Name: intel USB Blaster II interface Description: JTAG programmer cable from intel/altera (EZ-USB FX2 + EPM570) URL: https://www.intel.com/content/dam/www/programmable/us/en/pdfs/literature/ug/ug_usb_blstr_ii_cable.pdf + + +xvc-client: + + - Name: Xilinx Virtual Cable + Description: Xilinx Virtual Cable (XVC) is a TCP/IP-based protocol that acts like a JTAG cable. + URL: https://github.com/Xilinx/XilinxVirtualCable diff --git a/src/cable.hpp b/src/cable.hpp index 2cc1dae..9b95bbc 100644 --- a/src/cable.hpp +++ b/src/cable.hpp @@ -24,6 +24,7 @@ enum communication_type { MODE_USBBLASTER, /*! JTAG probe firmware for USBBLASTER */ MODE_CMSISDAP, /*! CMSIS-DAP JTAG probe */ MODE_DFU, /*! DFU based probe */ + MODE_XVC_CLIENT, /*! Xilinx Virtual Cable client */ }; typedef struct { @@ -67,6 +68,7 @@ static std::map cable_list = { {"tigard", {MODE_FTDI_SERIAL, {0x0403, 0x6010, INTERFACE_B, 0x08, 0x3B, 0x00, 0x00}}}, {"usb-blaster", {MODE_USBBLASTER, {0x09Fb, 0x6001, 0, 0, 0, 0, 0 }}}, {"usb-blasterII", {MODE_USBBLASTER, {0x09Fb, 0x6810, 0, 0, 0, 0, 0 }}}, + {"xvc-client", {MODE_XVC_CLIENT, {}}}, }; #endif diff --git a/src/jtag.cpp b/src/jtag.cpp index 70c5a61..8a55645 100644 --- a/src/jtag.cpp +++ b/src/jtag.cpp @@ -28,6 +28,9 @@ #include "dirtyJtag.hpp" #include "part.hpp" #include "usbBlaster.hpp" +#ifdef ENABLE_XVC_CLIENT +#include "xvc_client.hpp" +#endif using namespace std; @@ -115,6 +118,11 @@ void Jtag::init_internal(cable_t &cable, const string &dev, const string &serial case MODE_CMSISDAP: _jtag = new CmsisDAP(cable.config.vid, cable.config.pid, _verbose); break; +#endif +#ifdef ENABLE_XVC_CLIENT + case MODE_XVC_CLIENT: + _jtag = new XVC_client("127.0.0.1", clkHZ, _verbose); + break; #endif default: std::cerr << "Jtag: unknown cable type" << std::endl; diff --git a/src/xvc_client.cpp b/src/xvc_client.cpp new file mode 100644 index 0000000..ad511cb --- /dev/null +++ b/src/xvc_client.cpp @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2022 Gwenhael Goavec-Merou + */ + +#include "xvc_client.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "display.hpp" + +using namespace std; + +XVC_client::XVC_client(const std::string &ip_addr, uint32_t clkHz, + int8_t verbose): + _verbose(verbose > 0), _xfer_buf(NULL), _tms(NULL), _tditdo(NULL), + _num_bits(0), _last_tms(0), _last_tdi(0), _buffer_size(0), _sock(0) +{ + if (!open_connection(ip_addr)) + throw std::runtime_error("connection failure"); + + uint8_t buffer[2048]; + if (xfer_pkt("getinfo:", NULL, 0, buffer, 2048) <= 0) + throw std::runtime_error("can't read info"); + + std::regex r("[_:]"); + string rep((const char *)buffer); + + std::sregex_token_iterator start{ rep.begin(), rep.end(), r, -1 }, end; + std::vector toto(start, end); + + if (toto.size() != 3) + throw std::runtime_error("wrong getinfo: answer"); + + _server_name = std::move(toto[0]); + _server_vers = std::move(toto[1]); + _buffer_size = stoi(toto[2]) / 2; // buffer_size is for tms + tdi + + _xfer_buf = reinterpret_cast(malloc(sizeof(uint8_t) + * ((2*_buffer_size) + 4))); + _tms = reinterpret_cast(malloc(sizeof(uint8_t) * _buffer_size)); + _tditdo = reinterpret_cast(malloc(sizeof(uint8_t) * + _buffer_size)); + if (!_xfer_buf || !_tms || !_tditdo) + throw std::runtime_error("buffer allocation failure"); + + char disp[2048]; + snprintf(disp, sizeof(disp), "detected %s version %s packet size %u", + _server_name.c_str(), _server_vers.c_str(), _buffer_size); + + printInfo(disp); + + setClkFreq(clkHz); +} + +XVC_client::~XVC_client() +{ + // flush buffers before quit + if (_num_bits != 0) + flush(); + + // cleanup + if (_xfer_buf) + free(_xfer_buf); + if (_tms) + free(_tms); + if (_tditdo) + free(_tditdo); + // close socket + close(_sock); +} + +int XVC_client::writeTMS(uint8_t *tms, uint32_t len, bool flush_buffer) +{ + // empty buffer + // if asked flush + if (len == 0) + return ((flush_buffer) ? flush() : 0); + + for (uint32_t pos = 0; pos < len; pos++) { + // buffer full -> write + if (_num_bits == _buffer_size * 8) { + // write + ll_write(NULL); + _num_bits = 0; + } + + _last_tms = (tms[pos >> 3] & (1 << (pos & 0x07))) ? 1 : 0; + + if (_last_tms) + _tms[(_num_bits >> 3)] |= (1 << (_num_bits & 0x07)); + else + _tms[(_num_bits >> 3)] &= ~(1 << (_num_bits & 0x07)); + if (_last_tdi) + _tditdo[(_num_bits >> 3)] |= (1 << (_num_bits & 0x07)); + else + _tditdo[(_num_bits >> 3)] &= ~(1 << (_num_bits & 0x07)); + _num_bits++; + } + + // flush where it's asked or if the buffer is full + if (flush_buffer || _num_bits == _buffer_size * 8) + return flush(); + return len; +} + +int XVC_client::writeTDI(uint8_t *tx, uint8_t *rx, uint32_t len, bool end) +{ + if (len == 0) // nothing to do + return 0; + if (_num_bits != 0) // flush buffer to simplify next step + flush(); + + uint32_t xfer_len = _buffer_size * 8; // default to buffer capacity + uint8_t tms = (_last_tms) ? 0xff : 0x00; // set tms byte + uint8_t *tx_ptr = tx, *rx_ptr = rx; // use pointer to simplify algo + + /* write by burst */ + for (uint32_t rest = 0; rest < len; rest += xfer_len) { + if ((xfer_len + rest) > len) // len < buffer size + xfer_len = len - rest; // reduce xfer len + uint16_t tt = (xfer_len + 7) >> 3; // convert to Byte + memset(_tms, tms, tt); // fill tms buffer + memcpy(_tditdo, tx_ptr, tt); // fill tdi buffer + _num_bits = xfer_len; // set buffer size in bit + if (end && xfer_len + rest == len) { // last sequence: set tms 1 + _last_tms = 1; + uint16_t idx = _num_bits - 1; + _tms[(idx >> 3)] |= (1 << (idx & 0x07)); + } + ll_write((rx) ? rx_ptr : NULL); // write + + tx_ptr += tt; + if (rx) + rx_ptr += tt; + } + + return len; +} + +// toggle clk with constant TDI and TMS. More or less same idea as writeTDI +int XVC_client::toggleClk(uint8_t tms, uint8_t tdi, uint32_t clk_len) +{ + // nothing to do + if (clk_len == 0) + return 0; + if (_num_bits != 0) + flush(); + + _last_tms = tms; + _last_tdi = tdi; + uint8_t curr_tms = (tms) ? 0xff: 0x00; + uint8_t curr_tdi = (tdi) ? 0xff: 0x00; + + uint32_t len = clk_len; + + // flush buffer before starting + if (_num_bits != 0) + flush(); + + memset(_tditdo, curr_tdi, _buffer_size); + memset(_tms, curr_tms, _buffer_size); + do { + _num_bits = _buffer_size * 8; + if (len < _num_bits) + _num_bits = len; + len -= _num_bits; + ll_write(NULL); + } while (len > 0); + + return clk_len; +} + +int XVC_client::flush() +{ + return ll_write(NULL); +} + + +int XVC_client::setClkFreq(uint32_t clkHz) +{ + float clk_periodf = (1 / clkHz) * 1e9; + uint32_t clk_period = (uint32_t)round(clk_periodf); + + _xfer_buf[0] = static_cast((clk_period >> 0) & 0xff); + _xfer_buf[1] = static_cast((clk_period >> 8) & 0xff); + _xfer_buf[2] = static_cast((clk_period >> 16) & 0xff); + _xfer_buf[3] = static_cast((clk_period >> 24) & 0xff); + + if (xfer_pkt("settck:", _xfer_buf, 4, _xfer_buf, 4) <= 0) { + printError("setClkFreq: fail to configure frequency"); + return -EXIT_FAILURE; + } + + printf("freq %d\n", atoi((const char *)_xfer_buf)); + printf("%x %x %x %x\n", _xfer_buf[0], _xfer_buf[1], + _xfer_buf[2], _xfer_buf[3]); + + _clkHZ = clkHz; + return _clkHZ; +} + +bool XVC_client::open_connection(const string &ip_addr) +{ + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(2542); + addr.sin_addr.s_addr = inet_addr(ip_addr.c_str()); + + _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (_sock == -1) { + printError("Socket creation error"); + return false; + } + + if (connect(_sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) { + printError("Connection error"); + close(_sock); + return false; + } + + return true; +} + +ssize_t XVC_client::xfer_pkt(const string &instr, + const uint8_t *tx, uint32_t tx_size, + uint8_t *rx, uint32_t rx_size) +{ + ssize_t len = tx_size; + + /* 1. instruction */ + if (send(_sock, instr.c_str(), instr.size(), 0) == -1) { + printError("Send instruction failed"); + return -1; + } + + if (tx) { + if (send(_sock, tx, tx_size, 0) == -1) { + printError("Send error"); + return -1; + } + } + + if (rx) { + len = recv(_sock, rx, rx_size, 0); + if (len < 0) { + printError("Receive error"); + } else if (len == 0) { + fprintf(stderr, "Client orderly shut down the connection.\n"); + } + rx[len] = '\0'; + if (_verbose) { + printInfo("received " + std::to_string(len) + " Bytes (" + + std::to_string(len * 8) + ")"); + printf("\t"); + for (int i = 0; i < len; i++) + printf("%02x ", rx[i]); + printf("\n"); + } + } + + return len; +} + +bool XVC_client::ll_write(uint8_t *tdo) +{ + int ret; + if (_num_bits == 0) + return true; + uint32_t numbytes = (_num_bits + 7) >> 3; + + _xfer_buf[0] = static_cast((_num_bits >> 0) & 0xff); + _xfer_buf[1] = static_cast((_num_bits >> 8) & 0xff); + _xfer_buf[2] = static_cast((_num_bits >> 16) & 0xff); + _xfer_buf[3] = static_cast((_num_bits >> 24) & 0xff); + memcpy(_xfer_buf + 4, _tms, numbytes); + memcpy(_xfer_buf + 4 + numbytes, _tditdo, numbytes); + + if ((ret = xfer_pkt("shift:\0", _xfer_buf, (2 * numbytes) + 4, + _tditdo, numbytes)) < 0) + return false; + _num_bits = 0; // clear counter + + if (tdo) + memcpy(tdo, _tditdo, numbytes); + + return true; +} diff --git a/src/xvc_client.hpp b/src/xvc_client.hpp new file mode 100644 index 0000000..a1a1e67 --- /dev/null +++ b/src/xvc_client.hpp @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2022 Gwenhael Goavec-Merou + */ + +#ifndef SRC_XVC_CLIENT_HPP_ +#define SRC_XVC_CLIENT_HPP_ + +#include + +#include "jtagInterface.hpp" + +/*! + * \brief Xilinx Virtual Server client side probe driver + */ +class XVC_client: public JtagInterface { + public: + /*! + * \brief contructor: open device + * \param[in] ip_addr: server IP addr + * \param[in] clkHz: output clock frequency + * \param[in] verbose: verbose level -1 quiet, 0 normal, + * 1 verbose, 2 debug + */ + XVC_client(const std::string &ip_addr, uint32_t clkHz, int8_t verbose); + + ~XVC_client(); + + // jtagInterface requirement + /*! + * \brief configure probe clk frequency + * \param[in] clkHZ: frequency in Hertz + * \return <= 0 if something wrong, clkHZ otherwise + */ + int setClkFreq(uint32_t clkHZ) override; + + /*! + * \brief store a len tms bits in a buffer. send is only done if + * flush_buffer + * \param[in] tms: serie of tms state + * \param[in] len: number of tms bits + * \param[in] flush_buffer: force buffer to be send or not + * \return <= 0 if something wrong, len otherwise + */ + int writeTMS(uint8_t *tms, uint32_t len, bool flush_buffer) override; + + /*! + * \brief write and read len bits with optional tms set to 1 if end + * \param[in] tx: serie of tdi state to send + * \param[out] rx: buffer to store tdo bits from device + * \param[in] len: number of bit to read/write + * \param[in] end: if true tms is set to one with the last tdi bit + * \return <= 0 if something wrong, len otherwise + */ + int writeTDI(uint8_t *tx, uint8_t *rx, uint32_t len, bool end) override; + + /*! + * \brief send a serie of clock cycle with constant TMS and TDI + * \param[in] tms: tms state + * \param[in] tdi: tdi state + * \param[in] clk_len: number of clock cycle + * \return <= 0 if something wrong, clk_len otherwise + */ + int toggleClk(uint8_t tms, uint8_t tdi, uint32_t clk_len) override; + + /*! + * \brief flush internal buffer + * \return <=0 if something fail, > 0 otherwise + */ + int flush() override; + + /* + * unused + */ + int get_buffer_size() override { return 2048;} + bool isFull() override { return false;} + + std::string server_name() {return _server_name;} + std::string server_version() {return _server_vers;} + + private: + /*! + * \brief create a TCP socket and connect to + * server at ip_addr + * \param[in] ip_addr: server IP + * \return false if socket creation or connection fails + */ + bool open_connection(const std::string &ip_addr); + + /*! + * \brief sent instruction followed by tx buffer and read answer + * \param[in] instr: ascii instruction + * \param[in] tx: data buffer to send + * \param[in] tx_size: tx size (in Byte) + * \param[in] rx: server answer buffer (may be NULL) + * \param[in] rx_size: rx size (in Byte) or (0 when unused) + * \return -1 when error, 0 when disconnected or tx_size/rx_size + */ + ssize_t xfer_pkt(const std::string &instr, + const uint8_t *tx, uint32_t tx_size, + uint8_t *rx, uint32_t rx_size); + + /*! + * \brief lowlevel write: EMU_CMD_HW_JTAGx implementation + * \param[out]: tdo: TDO read buffer (may be null) + * \return false when failure + */ + bool ll_write(uint8_t *tdo); + + bool _verbose; /*!< display informations */ + + uint8_t *_xfer_buf; /*!> tx buffer */ + uint8_t *_tms; /*!< TMS internal buffer */ + uint8_t *_tditdo; /*!< TDI/TDO internal buffer */ + uint32_t _num_bits; /*!< number of bits stored */ + uint32_t _last_tms; /*!< last known TMS state */ + uint32_t _last_tdi; /*!< last known TDI state */ + + uint32_t _buffer_size; + std::string _server_name; + std::string _server_vers; + int _sock; +}; +#endif // SRC_XVC_CLIENT_HPP_