xvc_client: Xilinx Virtual Cable client PoC
This commit is contained in:
parent
3cc9adf2aa
commit
7bfce0fb2b
|
|
@ -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})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 <std::string, cable_t> 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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,304 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
* Copyright (C) 2022 Gwenhael Goavec-Merou <gwenhael.goavec-merou@trabucayre.com>
|
||||
*/
|
||||
|
||||
#include "xvc_client.hpp"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <strings.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <regex>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#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<std::string> 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<uint8_t *>(malloc(sizeof(uint8_t)
|
||||
* ((2*_buffer_size) + 4)));
|
||||
_tms = reinterpret_cast<uint8_t *>(malloc(sizeof(uint8_t) * _buffer_size));
|
||||
_tditdo = reinterpret_cast<uint8_t *>(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<uint8_t>((clk_period >> 0) & 0xff);
|
||||
_xfer_buf[1] = static_cast<uint8_t>((clk_period >> 8) & 0xff);
|
||||
_xfer_buf[2] = static_cast<uint8_t>((clk_period >> 16) & 0xff);
|
||||
_xfer_buf[3] = static_cast<uint8_t>((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<uint8_t>((_num_bits >> 0) & 0xff);
|
||||
_xfer_buf[1] = static_cast<uint8_t>((_num_bits >> 8) & 0xff);
|
||||
_xfer_buf[2] = static_cast<uint8_t>((_num_bits >> 16) & 0xff);
|
||||
_xfer_buf[3] = static_cast<uint8_t>((_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;
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
* Copyright (C) 2022 Gwenhael Goavec-Merou <gwenhael.goavec-merou@trabucayre.com>
|
||||
*/
|
||||
|
||||
#ifndef SRC_XVC_CLIENT_HPP_
|
||||
#define SRC_XVC_CLIENT_HPP_
|
||||
|
||||
#include <string>
|
||||
|
||||
#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_
|
||||
Loading…
Reference in New Issue