From 12955b8711bef419e540ad1f932dcfe4a3b84ae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20Ekstr=C3=B6m?= Date: Mon, 23 May 2022 20:16:48 +0200 Subject: [PATCH] Initial commit of libgpiod JTAG driver Not tested yet. --- CMakeLists.txt | 19 ++++ doc/cable.yml | 7 ++ src/cable.hpp | 2 + src/ftdiJtagBitbang.cpp | 17 ++- src/jtag.cpp | 10 ++ src/libgpiodJtagBitbang.cpp | 221 ++++++++++++++++++++++++++++++++++++ src/libgpiodJtagBitbang.hpp | 64 +++++++++++ src/main.cpp | 36 +++--- 8 files changed, 355 insertions(+), 21 deletions(-) create mode 100644 src/libgpiodJtagBitbang.cpp create mode 100644 src/libgpiodJtagBitbang.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c79ed19..883b624 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ else() option(ENABLE_UDEV "use udev to search JTAG adapter from /dev/xx" ON) endif() option(ENABLE_CMSISDAP "enable cmsis DAP interface (requires hidapi)" ON) +option(ENABLE_LIBGPIOD "enable libgpiod bitbang driver (requires libgpiod)" ON) option(USE_PKGCONFIG "Use pkgconfig to find libraries" ON) option(LINK_CMAKE_THREADS "Use CMake find_package to link the threading library" OFF) set(ISE_PATH "/opt/Xilinx/14.7" CACHE STRING "ise root directory (default: /opt/Xilinx/14.7)") @@ -61,6 +62,14 @@ if (USE_PKGCONFIG) set(ENABLE_UDEV OFF) endif() endif() + + if (ENABLE_LIBGPIOD) + pkg_check_modules(LIBGPIOD REQUIRED libgpiod) + if (NOT LIBGPIOD_FOUND) + message("libgpiod not found, disabling gpiod support") + set(ENABLE_LIBGPIOD OFF) + endif() + endif() endif() set(OPENFPGALOADER_SOURCE @@ -193,6 +202,16 @@ if (ENABLE_UDEV) target_link_libraries(openFPGALoader ${LIBUDEV_LIBRARIES}) endif() +if (ENABLE_LIBGPIOD) + link_directories(${LIBGPIOD_LIBRARY_DIRS}) + include_directories(${LIBGPIOD_INCLUDE_DIRS}) + target_link_libraries(openFPGALoader ${LIBGPIOD_LIBRARIES}) + add_definitions(-DENABLE_LIBGPIOD=1) + target_sources(openFPGALoader PRIVATE src/libgpiodJtagBitbang.cpp) + list (APPEND OPENFPGALOADER_HEADERS src/libgpiodJtagBitbang.hpp) + message("libgpiod support enabled") +endif(ENABLE_LIBGPIOD) + if (BUILD_STATIC) set_target_properties(openFPGALoader PROPERTIES LINK_SEARCH_END_STATIC 1) endif() diff --git a/doc/cable.yml b/doc/cable.yml index 6f9da3e..c93b391 100644 --- a/doc/cable.yml +++ b/doc/cable.yml @@ -236,3 +236,10 @@ xvc-server: - Name: Xilinx Virtual Cable (server side) Description: Xilinx Virtual Cable (XVC) is a TCP/IP-based protocol that acts like a JTAG cable. URL: https://github.com/Xilinx/XilinxVirtualCable + + +libgpiod: + + - Name: Bitbang GPIO + Description: Bitbang GPIO pins on Linux host. + URL: https://github.com/brgl/libgpiod diff --git a/src/cable.hpp b/src/cable.hpp index 4b5dc96..c9cace7 100644 --- a/src/cable.hpp +++ b/src/cable.hpp @@ -25,6 +25,7 @@ enum communication_type { MODE_CMSISDAP, /*! CMSIS-DAP JTAG probe */ MODE_DFU, /*! DFU based probe */ MODE_XVC_CLIENT, /*! Xilinx Virtual Cable client */ + MODE_LIBGPIOD_BITBANG, /*! Bitbang gpio pins */ }; typedef struct { @@ -69,6 +70,7 @@ static std::map cable_list = { {"usb-blaster", {MODE_USBBLASTER, {0x09Fb, 0x6001, 0, 0, 0, 0, 0, 0}}}, {"usb-blasterII", {MODE_USBBLASTER, {0x09Fb, 0x6810, 0, 0, 0, 0, 0, 0}}}, {"xvc-client", {MODE_XVC_CLIENT, {}}}, + {"libgpiod", {MODE_LIBGPIOD_BITBANG, {}}}, }; #endif diff --git a/src/ftdiJtagBitbang.cpp b/src/ftdiJtagBitbang.cpp index 58f9031..d2a9117 100644 --- a/src/ftdiJtagBitbang.cpp +++ b/src/ftdiJtagBitbang.cpp @@ -38,10 +38,19 @@ FtdiJtagBitBang::FtdiJtagBitBang(const FTDIpp_MPSSE::mpsse_bit_config &cable, { unsigned char *ptr; - _tck_pin = pin_conf->tck_pin; - _tms_pin = pin_conf->tms_pin; - _tdi_pin = pin_conf->tdi_pin; - _tdo_pin = pin_conf->tdo_pin; + _tck_pin = 1 << pin_conf->tck_pin; + _tms_pin = 1 << pin_conf->tms_pin; + _tdi_pin = 1 << pin_conf->tdi_pin; + _tdo_pin = 1 << pin_conf->tdo_pin; + + /* Validate pins */ + uint8_t pins[] = {_tck_pin, _tms_pin, _tdi_pin, _tdo_pin}; + for (int i = 0; i < sizeof(pins) / sizeof(pins[0]); i++) { + if (pins[i] > FT232RL_RI || pins[i] < FT232RL_TXD) { + printError("Invalid pin ID"); + throw std::exception(); + } + } /* store FTDI TX Fifo size */ if (_pid == 0x6001) // FT232R diff --git a/src/jtag.cpp b/src/jtag.cpp index 347f5a5..e4d23cc 100644 --- a/src/jtag.cpp +++ b/src/jtag.cpp @@ -21,6 +21,9 @@ #include "ftdipp_mpsse.hpp" #include "ftdiJtagBitbang.hpp" #include "ftdiJtagMPSSE.hpp" +#ifdef ENABLE_LIBGPIOD +#include "libgpiodJtagBitbang.hpp" +#endif #include "jlink.hpp" #ifdef ENABLE_CMSISDAP #include "cmsisDAP.hpp" @@ -130,6 +133,13 @@ void Jtag::init_internal(cable_t &cable, const string &dev, const string &serial #else std::cerr << "Jtag: support for xvc-client was not enabled at compile time" << std::endl; throw std::exception(); +#endif +#ifdef ENABLE_LIBGPIOD + case MODE_LIBGPIOD_BITBANG: + if (pin_conf == NULL) + throw std::exception(); + _jtag = new LibgpiodJtagBitbang(pin_conf, dev, clkHZ, _verbose); + break; #endif default: std::cerr << "Jtag: unknown cable type" << std::endl; diff --git a/src/libgpiodJtagBitbang.cpp b/src/libgpiodJtagBitbang.cpp new file mode 100644 index 0000000..0dd7628 --- /dev/null +++ b/src/libgpiodJtagBitbang.cpp @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2020 Gwenhael Goavec-Merou + * + * libgpiod bitbang driver added by Niklas Ekström in 2022 + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "display.hpp" +#include "libgpiodJtagBitbang.hpp" + +#define DEBUG 1 + +#ifdef DEBUG +#define display(...) \ + do { \ + if (_verbose) fprintf(stdout, __VA_ARGS__); \ + }while(0) +#else +#define display(...) do {}while(0) +#endif + +LibgpiodJtagBitbang::LibgpiodJtagBitbang( + const jtag_pins_conf_t *pin_conf, + const std::string &dev, uint32_t clkHZ, uint8_t verbose) +{ + _verbose = verbose; + + _tck_pin = pin_conf->tck_pin; + _tms_pin = pin_conf->tms_pin; + _tdi_pin = pin_conf->tdi_pin; + _tdo_pin = pin_conf->tdo_pin; + + std::string chip_dev = dev; + if (chip_dev.empty()) + chip_dev = "/dev/gpiochip0"; + + display("libgpiod jtag bitbang driver, dev=%s, tck_pin=%d, tms_pin=%d, tdi_pin=%d, tdo_pin=%d\n", + chip_dev.c_str(), _tck_pin, _tms_pin, _tdi_pin, _tdo_pin); + + if (chip_dev.length() < 14 || chip_dev.substr(0, 13) != "/dev/gpiochip") { + display("Invalid gpio chip %s, should be /dev/gpiochipX\n", chip_dev.c_str()); + throw std::runtime_error("Invalid gpio chip\n"); + } + + /* Validate pins */ + int pins[] = {_tck_pin, _tms_pin, _tdi_pin, _tdo_pin}; + for (int i = 0; i < sizeof(pins) / sizeof(pins[0]); i++) { + if (pins[i] < 0 || pins[i] >= 1000) { + display("Pin %d is outside of valid range\n", pins[i]); + throw std::runtime_error("A pin is outside of valid range\n"); + } + + for (int j = i + 1; j < sizeof(pins) / sizeof(pins[0]); j++) { + if (pins[i] == pins[j]) { + display("Two or more pins are assigned to the same pin number %d\n", pins[i]); + throw std::runtime_error("Two or more pins are assigned to the same pin number\n"); + } + } + } + + _chip = gpiod_chip_open_by_name(chip_dev.substr(5).c_str()); + if (!_chip) { + display("Unable to open gpio chip %s\n", chip_dev.c_str()); + throw std::runtime_error("Unable to open gpio chip\n"); + } + + _tdo_line = get_line(_tdo_pin, 0, GPIOD_LINE_REQUEST_DIRECTION_INPUT); + _tdi_line = get_line(_tdi_pin, 0, GPIOD_LINE_REQUEST_DIRECTION_OUTPUT); + _tck_line = get_line(_tck_pin, 0, GPIOD_LINE_REQUEST_DIRECTION_OUTPUT); + _tms_line = get_line(_tms_pin, 1, GPIOD_LINE_REQUEST_DIRECTION_OUTPUT); + + _curr_tdi = 0; + _curr_tck = 0; + _curr_tms = 1; +} + +LibgpiodJtagBitbang::~LibgpiodJtagBitbang() +{ + if (_tms_line) + gpiod_line_release(_tms_line); + + if (_tck_line) + gpiod_line_release(_tck_line); + + if (_tdi_line) + gpiod_line_release(_tdi_line); + + if (_tdo_line) + gpiod_line_release(_tdo_line); + + if (_chip) + gpiod_chip_close(_chip); +} + +gpiod_line *LibgpiodJtagBitbang::get_line(unsigned int offset, int val, int dir) +{ + gpiod_line *line = gpiod_chip_get_line(_chip, offset); + if (!line) { + display("Unable to get gpio line %d\n", offset); + throw std::runtime_error("Unable to get gpio line\n"); + } + + gpiod_line_request_config config = { + .consumer = "openFPGALoader", + .request_type = dir, + .flags = 0, + }; + + int ret = gpiod_line_request(line, &config, val); + if (ret < 0) { + display("Error requesting gpio line %d\n", offset); + throw std::runtime_error("Error requesting gpio line\n"); + } + + return line; +} + +int LibgpiodJtagBitbang::update_pins(int tck, int tms, int tdi) +{ + if (tdi != _curr_tdi) { + if (gpiod_line_set_value(_tdi_line, tdi) < 0) + display("Unable to set gpio pin tdi\n"); + } + + if (tms != _curr_tms) { + if (gpiod_line_set_value(_tms_line, tms) < 0) + display("Unable to set gpio pin tms\n"); + } + + if (tck != _curr_tck) { + if (gpiod_line_set_value(_tck_line, tck) < 0) + display("Unable to set gpio pin tck\n"); + } + + _curr_tdi = tdi; + _curr_tms = tms; + _curr_tck = tck; + + return 0; +} + +int LibgpiodJtagBitbang::read_tdo() +{ + return gpiod_line_get_value(_tdo_line); +} + +int LibgpiodJtagBitbang::setClkFreq(uint32_t clkHZ) +{ + // FIXME: The assumption is that calling the gpiod_line_set_value + // routine will limit the clock frequency to lower than what is specified. + // This needs to be verified, and possibly artificial delays should be added. + return 0; +} + +int LibgpiodJtagBitbang::writeTMS(uint8_t *tms_buf, uint32_t len, bool flush_buffer) +{ + int tms; + + for (uint32_t i = 0; i < len; i++) { + tms = ((tms_buf[i >> 3] & (1 << (i & 7))) ? 1 : 0); + + update_pins(0, tms, 0); + update_pins(1, tms, 0); + } + + update_pins(0, tms, 0); + + return len; +} + +int LibgpiodJtagBitbang::writeTDI(uint8_t *tx, uint8_t *rx, uint32_t len, bool end) +{ + int tms = _curr_tms; + int tdi = _curr_tdi; + + if (rx) + memset(rx, 0, len / 8); + + for (uint32_t i = 0, pos = 0; i < len; i++) { + if (end && (i == len - 1)) + tms = 1; + + tdi = (tx[i >> 3] & (1 << (i & 7))) ? 1 : 0; + + update_pins(0, tms, tdi); + + if (rx) { + if (read_tdo() > 0) + rx[i >> 3] |= 1 << (i & 7); + } + + update_pins(1, tms, tdi); + } + + update_pins(0, tms, tdi); + + return len; +} + +int LibgpiodJtagBitbang::toggleClk(uint8_t tms, uint8_t tdi, uint32_t clk_len) +{ + for (uint32_t i = 0; i < clk_len; i++) { + update_pins(0, tms, tdi); + update_pins(1, tms, tdi); + } + + update_pins(0, tms, tdi); + + return clk_len; +} diff --git a/src/libgpiodJtagBitbang.hpp b/src/libgpiodJtagBitbang.hpp new file mode 100644 index 0000000..5b2fdae --- /dev/null +++ b/src/libgpiodJtagBitbang.hpp @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2020 Gwenhael Goavec-Merou + * + * libgpiod bitbang driver added by Niklas Ekström in 2022 + */ + +#ifndef LIBGPIODBITBANG_H +#define LIBGPIODBITBANG_H + +#include + +#include "board.hpp" +#include "jtagInterface.hpp" + +/*! + * \file LibgpiodJtagBitbang.hpp + * \class LibgpiodJtagBitbang + * \brief concrete class between jtag implementation and gpio bitbang + * \author Niklas Ekström + */ + +struct gpiod_chip; +struct gpiod_line; + +class LibgpiodJtagBitbang : public JtagInterface { + public: + LibgpiodJtagBitbang(const jtag_pins_conf_t *pin_conf, + const std::string &dev, uint32_t clkHZ, uint8_t verbose); + virtual ~LibgpiodJtagBitbang(); + + int setClkFreq(uint32_t clkHZ) override; + int writeTMS(uint8_t *tms_buf, uint32_t len, bool flush_buffer) override; + int writeTDI(uint8_t *tx, uint8_t *rx, uint32_t len, bool end) override; + 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: + gpiod_line *get_line(unsigned int offset, int val, int dir); + int update_pins(int tck, int tms, int tdi); + int read_tdo(); + + bool _verbose; + + int _tck_pin; + int _tms_pin; + int _tdo_pin; + int _tdi_pin; + + gpiod_chip *_chip; + + gpiod_line *_tck_line; + gpiod_line *_tms_line; + gpiod_line *_tdo_line; + gpiod_line *_tdi_line; + + int _curr_tms; + int _curr_tdi; + int _curr_tck; +}; +#endif diff --git a/src/main.cpp b/src/main.cpp index 65f7d49..29e070a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -617,6 +617,14 @@ static int parse_eng(string arg, double *dst) { } } +static int get_bit_index(int mask) +{ + for (int i = 0; i < 32; i++) + if (mask & (1 << i)) + return i; + return -1; +} + /* arguments parser */ int parse_opt(int argc, char **argv, struct arguments *args, jtag_pins_conf_t *pins_config) { @@ -649,7 +657,7 @@ int parse_opt(int argc, char **argv, struct arguments *args, jtag_pins_conf_t *p ("ftdi-serial", "FTDI chip serial number", cxxopts::value(args->ftdi_serial)) ("ftdi-channel", "FTDI chip channel number (channels 0-3 map to A-D)", cxxopts::value(args->ftdi_channel)) -#ifdef USE_UDEV +#if defined(USE_UDEV) || defined(ENABLE_LIBGPIOD) ("d,device", "device to use (/dev/ttyUSBx)", cxxopts::value(args->device)) #endif @@ -683,7 +691,7 @@ int parse_opt(int argc, char **argv, struct arguments *args, jtag_pins_conf_t *p "write bitstream in SRAM (default: true)") ("o,offset", "start offset in EEPROM", cxxopts::value(args->offset)) - ("pins", "pin config (only for ft232R) TDI:TDO:TCK:TMS", + ("pins", "pin config TDI:TDO:TCK:TMS", cxxopts::value>(pins)) ("probe-firmware", "firmware for JTAG probe (usbBlasterII)", cxxopts::value(args->probe_firmware)) @@ -788,20 +796,19 @@ int parse_opt(int argc, char **argv, struct arguments *args, jtag_pins_conf_t *p } static std::map pins_list = { - {"TXD", FT232RL_TXD}, - {"RXD", FT232RL_RXD}, - {"RTS", FT232RL_RTS}, - {"CTS", FT232RL_CTS}, - {"DTR", FT232RL_DTR}, - {"DSR", FT232RL_DSR}, - {"DCD", FT232RL_DCD}, - {"RI" , FT232RL_RI }}; - + {"TXD", get_bit_index(FT232RL_TXD)}, + {"RXD", get_bit_index(FT232RL_RXD)}, + {"RTS", get_bit_index(FT232RL_RTS)}, + {"CTS", get_bit_index(FT232RL_CTS)}, + {"DTR", get_bit_index(FT232RL_DTR)}, + {"DSR", get_bit_index(FT232RL_DSR)}, + {"DCD", get_bit_index(FT232RL_DCD)}, + {"RI" , get_bit_index(FT232RL_RI) }}; for (int i = 0; i < 4; i++) { int pin_num; try { - pin_num = 1 << std::stoi(pins[i], nullptr, 0); + pin_num = std::stoi(pins[i], nullptr, 0); } catch (std::exception &e) { if (pins_list.find(pins[i]) == pins_list.end()) { printError("Invalid pin name"); @@ -810,11 +817,6 @@ int parse_opt(int argc, char **argv, struct arguments *args, jtag_pins_conf_t *p pin_num = pins_list[pins[i]]; } - if (pin_num > FT232RL_RI || pin_num < FT232RL_TXD) { - printError("Invalid pin ID"); - throw std::exception(); - } - switch (i) { case 0: pins_config->tdi_pin = pin_num;