diff --git a/CMakeLists.txt b/CMakeLists.txt index 93f1a0b..9119e97 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,8 +13,10 @@ endif() option(ENABLE_CMSISDAP "enable cmsis DAP interface (requires hidapi)" ON) if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") option(ENABLE_LIBGPIOD "enable libgpiod bitbang driver (requires libgpiod)" ON) + option(ENABLE_REMOTEBITBANG "enable remote bitbang driver" ON) else() set(ENABLE_LIBGPIOD OFF) + set(ENABLE_REMOTEBITBANG OFF) endif() option(USE_PKGCONFIG "Use pkgconfig to find libraries" ON) option(LINK_CMAKE_THREADS "Use CMake find_package to link the threading library" OFF) @@ -254,13 +256,22 @@ endif(ENABLE_CMSISDAP) if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") add_definitions(-DENABLE_XVC=1) target_sources(openFPGALoader PRIVATE src/xvc_client.cpp src/xvc_server.cpp) - list (APPEND OPENFPGALOADER_HEADERS src/xvc_client.hpp src/xvc_server.cpp) + list (APPEND OPENFPGALOADER_HEADERS src/xvc_client.hpp src/xvc_server.hpp) set(CMAKE_EXE_LINKER_FLAGS "-pthread ${CMAKE_EXE_LINKER_FLAGS}") message("Xilinx Virtual Server support enabled") else() message("Xilinx Virtual Server support disabled") endif() +if (ENABLE_REMOTEBITBANG) + add_definitions(-DENABLE_REMOTEBITBANG=1) + target_sources(openFPGALoader PRIVATE src/remoteBitbang_client.cpp) + list (APPEND OPENFPGALOADER_HEADERS src/remoteBitbang_client.hpp) + message("Remote bitbang client support enabled") +else() + message("Remote bitbang client 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 ae96f1b..27c049c 100644 --- a/doc/cable.yml +++ b/doc/cable.yml @@ -218,6 +218,13 @@ steppenprobe: URL: https://github.com/diegoherranz/steppenprobe +remote-bitgang: + + - Name: OpenOCD remote bitbang + Description: The remote_bitbang JTAG driver is used to drive JTAG from a remote (TCP) process + URL: https://github.com/openocd-org/openocd/blob/master/doc/manual/jtag/drivers/remote_bitbang.txt + + tigard: - Name: tigard @@ -262,4 +269,4 @@ 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 \ No newline at end of file + URL: https://github.com/jwatte/jetson-gpio-example diff --git a/src/cable.hpp b/src/cable.hpp index 2ef49f5..f2cf2df 100644 --- a/src/cable.hpp +++ b/src/cable.hpp @@ -25,6 +25,7 @@ enum communication_type { MODE_XVC_CLIENT, /*! Xilinx Virtual Cable client */ MODE_LIBGPIOD_BITBANG, /*! Bitbang gpio pins */ MODE_JETSONNANO_BITBANG, /*! Bitbang gpio pins */ + MODE_REMOTEBITBANG, /*! Remote Bitbang mode */ }; /*! @@ -119,6 +120,9 @@ static std::map cable_list = { #ifdef ENABLE_JETSONNANOGPIO {"jetson-nano-gpio", {MODE_JETSONNANO_BITBANG, {}}}, #endif +#ifdef ENABLE_REMOTEBITBANG + {"remote-bitbang", CABLE_DEF(MODE_REMOTEBITBANG, 0x0000, 0x0000 )}, +#endif }; #endif // SRC_CABLE_HPP_ diff --git a/src/jtag.cpp b/src/jtag.cpp index 9a28b8e..cc9358b 100644 --- a/src/jtag.cpp +++ b/src/jtag.cpp @@ -29,6 +29,9 @@ #endif #include "dirtyJtag.hpp" #include "part.hpp" +#ifdef ENABLE_REMOTEBITBANG +#include "remoteBitbang_client.hpp" +#endif #include "usbBlaster.hpp" #ifdef ENABLE_XVC #include "xvc_client.hpp" @@ -141,6 +144,11 @@ void Jtag::init_internal(const cable_t &cable, const string &dev, const string & case MODE_JETSONNANO_BITBANG: _jtag = new JetsonNanoJtagBitbang(pin_conf, dev, clkHZ, _verbose); break; +#endif +#ifdef ENABLE_REMOTEBITBANG + case MODE_REMOTEBITBANG: + _jtag = new RemoteBitbang_client(ip_adr, port, _verbose); + break; #endif default: std::cerr << "Jtag: unknown cable type" << std::endl; diff --git a/src/remoteBitbang_client.cpp b/src/remoteBitbang_client.cpp new file mode 100644 index 0000000..40d6357 --- /dev/null +++ b/src/remoteBitbang_client.cpp @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2023 Gwenhael Goavec-Merou + */ + +#include "remoteBitbang_client.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "display.hpp" + +using namespace std; + +#define TCK_OFFSET 2 +#define TMS_OFFSET 1 +#define TDI_OFFSET 0 +#define TCK_BIT (1 << TCK_OFFSET) +#define TMS_BIT (1 << TMS_OFFSET) +#define TDI_BIT (1 << TDI_OFFSET) + +RemoteBitbang_client::RemoteBitbang_client(const std::string &ip_addr, int port, + int8_t verbose): + _xfer_buf(NULL), _num_bytes(0), _last_tms(TMS_BIT), + _last_tdi(0), _buffer_size(2048), _sock(0), _port(port) +{ + (void) verbose; + /* create client to server */ + if (!open_connection(ip_addr)) + throw std::runtime_error("connection failure"); + + /* set led to low */ + if (xfer_pkt('b', NULL) < 0) + throw std::runtime_error("can't set led low"); + + _xfer_buf = reinterpret_cast(malloc(sizeof(uint8_t) + * _buffer_size)); + if (!_xfer_buf) + throw std::runtime_error("can't allocate internal buffer"); +} + +RemoteBitbang_client::~RemoteBitbang_client() +{ + // flush buffers before quit + if (_num_bytes != 0) + flush(); + + // set led high + if (xfer_pkt('B', NULL) < 0) + printf("can't set led high"); + + // send close request + if (xfer_pkt('Q', NULL) < 0) + printf("can't send close request"); + + // cleanup + if (_xfer_buf) + free(_xfer_buf); + // close socket + close(_sock); +} + +int RemoteBitbang_client::writeTMS(uint8_t *tms, uint32_t len, + bool flush_buffer) +{ + // empty buffer + // if asked flush + if (len == 0) + return ((flush_buffer) ? flush() : 0); + + uint8_t base_v = '0' + _last_tdi; + for (uint32_t pos = 0; pos < len; pos++) { + // buffer full -> write + if (_num_bytes == _buffer_size) + ll_write(NULL); + _last_tms = (tms[pos >> 3] & (1 << (pos & 0x07))) ? TMS_BIT : 0; + _xfer_buf[_num_bytes++] = base_v + _last_tms; + _xfer_buf[_num_bytes++] = base_v + _last_tms + TCK_BIT; + } + + // flush where it's asked or if the buffer is full + if (flush_buffer || _num_bytes == _buffer_size * 8) + return flush(); + return len; +} + +int RemoteBitbang_client::writeTDI(uint8_t *tx, uint8_t *rx, uint32_t len, + bool end) +{ + if (len == 0) // nothing to do + return 0; + + uint8_t base_v = '0' + _last_tms; + for (uint32_t pos = 0; pos < len; pos++) { + if (_num_bytes == _buffer_size) + ll_write(NULL); // NULL because _num_bytes is always 0 when read + _last_tdi = (tx[pos >> 3] & (1 << (pos & 0x07))) ? TDI_BIT : 0; + if (end && pos == len - 1) { + _last_tms = TMS_BIT; + base_v = '0' + _last_tms; + } + _xfer_buf[_num_bytes++] = base_v + _last_tdi; + _xfer_buf[_num_bytes++] = base_v + _last_tdi + TCK_BIT; + if (rx) { + uint8_t tdo; + ll_write(&tdo); + if (tdo == '1') + rx[pos >> 3] |= (1 << (pos & 0x07)); + else + rx[pos >> 3] &= ~(1 << (pos & 0x07)); + } + } + + return len; +} + +// toggle clk with constant TDI and TMS. +int RemoteBitbang_client::toggleClk(uint8_t tms, uint8_t tdi, uint32_t clk_len) +{ + // nothing to do + if (clk_len == 0) + return 0; + if (_num_bytes != 0) + flush(); + + _last_tms = tms; + _last_tdi = tdi; + uint8_t val = (_last_tms | _last_tdi); + + // flush buffer before starting + if (_num_bytes != 0) + flush(); + + for (uint32_t len = 0; len < clk_len; len++) { + if (len == _num_bytes) + ll_write(NULL); + _xfer_buf[_num_bytes++] = '0' + val; + _xfer_buf[_num_bytes++] = '0' + (val | TCK_BIT); + } + + ll_write(NULL); + + return clk_len; +} + +int RemoteBitbang_client::flush() +{ + return ll_write(NULL); +} + +int RemoteBitbang_client::setClkFreq(uint32_t clkHz) +{ + printWarn("clock speed is not configurable"); + return clkHz; +} + +bool RemoteBitbang_client::open_connection(const string &ip_addr) +{ + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(_port); + 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; + } + + int one = 1; + setsockopt(_sock, IPPROTO_TCP, TCP_NODELAY, (const char *)&one, + sizeof(one)); + + return true; +} + +ssize_t RemoteBitbang_client::xfer_pkt(uint8_t instr, uint8_t *rx) +{ + ssize_t len; + // 1. instruction + if ((len = write(_sock, &instr, 1)) == -1) { + printError("Send instruction failed with error " + + std::to_string(len)); + return -1; + } + + if (rx) { + len = recv(_sock, rx, 1, 0); + if (len < 0) { + printError("Receive error"); + return len; + } + } + + return (rx) ? 1 : 0; +} + +bool RemoteBitbang_client::ll_write(uint8_t *tdo) +{ + if (_num_bytes == 0) + return true; + + ssize_t len; + // write current buffer + if ((len = write(_sock, _xfer_buf, _num_bytes)) == -1) { + printError("Send error error: " + std::to_string(len)); + return false; + } + _num_bytes = 0; + + // read only one char (if tdo is not null + if (tdo) { + if (xfer_pkt('R', tdo) < 0) { + printError("read request error"); + return false; + } + } + + return true; +} diff --git a/src/remoteBitbang_client.hpp b/src/remoteBitbang_client.hpp new file mode 100644 index 0000000..499ded4 --- /dev/null +++ b/src/remoteBitbang_client.hpp @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2023 Gwenhael Goavec-Merou + */ + +#ifndef SRC_REMOTEBITBANG_CLIENT_HPP_ +#define SRC_REMOTEBITBANG_CLIENT_HPP_ + +#include + +#include "jtagInterface.hpp" + +/*! + * \brief Remote Bitbang Protocol client side driver + */ +class RemoteBitbang_client: public JtagInterface { + public: + /*! + * \brief constructor: open device + * \param[in] ip_addr: server IP addr + * \param[in] port : server port + * \param[in] verbose: verbose level -1 quiet, 0 normal, + * 1 verbose, 2 debug + */ + RemoteBitbang_client(const std::string &ip_addr, int port, + int8_t verbose); + + ~RemoteBitbang_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 _buffer_size;} + bool isFull() override { return _buffer_size == _num_bytes;} + + 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 one instruction (ASCII format) and read when requested + * \param[in] instr: ascii instruction + * \param[in] rx: server answer buffer (may be NULL) + * \return -1 when error, 0 when disconnected or tx_size/rx_size + */ + ssize_t xfer_pkt(uint8_t instr, uint8_t *rx); + + /*! + * \brief lowlevel write: write internal buffer (ASCII format) + * and read one char when tdo is not NULL. + * \param[out]: tdo: TDO read pointer (may be null) + * \return false when failure + */ + bool ll_write(uint8_t *tdo); + + uint8_t *_xfer_buf; /*!< tx buffer */ + uint32_t _num_bytes; /*!< number of bits stored */ + uint32_t _last_tms; /*!< last known TMS state */ + uint32_t _last_tdi; /*!< last known TDI state */ + + uint32_t _buffer_size; /*!< buffer max capacity */ + int _sock; /*!< socket */ + int _port; /*!< target port */ +}; +#endif // SRC_REMOTEBITBANG_CLIENT_HPP_