openFPGALoader/src/jtag.cpp

691 lines
16 KiB
C++

// SPDX-License-Identifier: Apache-2.0
/*
* Copyright (C) 2020 Gwenhael Goavec-Merou <gwenhael.goavec-merou@trabucayre.com>
*/
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <sstream>
#include <unistd.h>
#include <vector>
#include <string>
#include "anlogicCable.hpp"
#ifdef USE_LIBFTDI
#include "ch552_jtag.hpp"
#endif
#include "display.hpp"
#include "jtag.hpp"
#ifdef USE_LIBFTDI
#include "ftdiJtagBitbang.hpp"
#include "ftdiJtagMPSSE.hpp"
#endif
#ifdef ENABLE_GOWIN_GWU2X
#include "gwu2x_jtag.hpp"
#endif
#ifdef ENABLE_LIBGPIOD
#include "libgpiodJtagBitbang.hpp"
#endif
#ifdef ENABLE_JETSONNANOGPIO
#include "jetsonNanoJtagBitbang.hpp"
#endif
#include "jlink.hpp"
#ifdef ENABLE_CMSISDAP
#include "cmsisDAP.hpp"
#endif
#include "dirtyJtag.hpp"
#include "esp_usb_jtag.hpp"
#include "ch347jtag.hpp"
#include "part.hpp"
#ifdef ENABLE_REMOTEBITBANG
#include "remoteBitbang_client.hpp"
#endif
#include "usbBlaster.hpp"
#ifdef ENABLE_XVC
#include "xvc_client.hpp"
#endif
using namespace std;
#define DEBUG 0
#if DEBUG
#define display(...) \
do { if (_verbose) fprintf(stdout, __VA_ARGS__);}while(0)
#else
#define display(...) do {}while(0)
#endif
/*
* FT232 JTAG PINS MAPPING:
* AD0 -> TCK
* AD1 -> TDI
* AD2 -> TDO
* AD3 -> TMS
*/
/* Rmq:
* pour TMS: l'envoi de n necessite de mettre n-1 comme longueur
* mais le bit n+1 est utilise pour l'etat suivant le dernier
* front. Donc il faut envoyer 6bits ([5:0]) pertinents pour
* utiliser le bit 6 comme etat apres la commande,
* le bit 7 corresponds a l'etat de TDI (donc si on fait 7 cycles
* l'etat de TDI va donner l'etat de TMS...)
* transfert/lecture: le dernier bit de IR ou DR doit etre envoye en
* meme temps que le TMS qui fait sortir de l'etat donc il faut
* pour n bits a transferer :
* - envoyer 8bits * (n/8)-1
* - envoyer les 7 bits du dernier octet;
* - envoyer le dernier avec 0x4B ou 0x6B
*/
Jtag::Jtag(const cable_t &cable, const jtag_pins_conf_t *pin_conf,
const string &dev,
const string &serial, uint32_t clkHZ, int8_t verbose,
const string &ip_adr, int port,
const bool invert_read_edge, const string &firmware_path,
const std::map<uint32_t, misc_device> &user_misc_devs):
_verbose(verbose > 1),
_state(RUN_TEST_IDLE),
_tms_buffer_size(128), _num_tms(0),
_board_name("nope"), _user_misc_devs(user_misc_devs),
device_index(0), _dr_bits_before(0), _dr_bits_after(0),
_ir_bits_before(0), _ir_bits_after(0), _curr_tdi(1)
{
switch (cable.type) {
case MODE_ANLOGICCABLE:
_jtag = new AnlogicCable(clkHZ);
break;
#ifdef USE_LIBFTDI
case MODE_FTDI_BITBANG:
if (pin_conf == NULL)
throw std::exception();
_jtag = new FtdiJtagBitBang(cable, pin_conf, dev, serial, clkHZ, verbose);
break;
case MODE_FTDI_SERIAL:
_jtag = new FtdiJtagMPSSE(cable, dev, serial, clkHZ,
invert_read_edge, verbose);
break;
case MODE_CH552_JTAG:
_jtag = new CH552_jtag(cable, dev, serial, clkHZ, verbose);
break;
#endif
case MODE_CH347:
_jtag = new CH347Jtag(clkHZ, verbose, cable.vid, cable.pid, cable.bus_addr, cable.device_addr);
break;
case MODE_DIRTYJTAG:
_jtag = new DirtyJtag(clkHZ, verbose);
break;
#ifdef ENABLE_GOWIN_GWU2X
case MODE_GWU2X:
_jtag = new GowinGWU2x((cable_t *)&cable, clkHZ, verbose);
break;
#else
std::cerr << "Jtag: support for Gowin GWU2X was not enabled at compile time" << std::endl;
throw std::exception();
#endif
case MODE_JLINK:
_jtag = new Jlink(clkHZ, verbose, cable.vid, cable.pid);
break;
case MODE_ESP:
_jtag = new esp_usb_jtag(clkHZ, verbose, 0x303a, 0x1001);
break;
case MODE_USBBLASTER:
_jtag = new UsbBlaster(cable, firmware_path, verbose);
break;
case MODE_CMSISDAP:
#ifdef ENABLE_CMSISDAP
_jtag = new CmsisDAP(cable, cable.config.index, verbose);
break;
#else
std::cerr << "Jtag: support for cmsisdap was not enabled at compile time" << std::endl;
throw std::exception();
#endif
case MODE_XVC_CLIENT:
#ifdef ENABLE_XVC
_jtag = new XVC_client(ip_adr, port, clkHZ, verbose);
break;
#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:
_jtag = new LibgpiodJtagBitbang(pin_conf, dev, clkHZ, verbose);
break;
#endif
#ifdef ENABLE_JETSONNANOGPIO
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;
throw std::exception();
}
_tms_buffer = (unsigned char *)malloc(sizeof(unsigned char) * _tms_buffer_size);
if (_tms_buffer == nullptr)
throw std::runtime_error("Error: memory allocation failed");
memset(_tms_buffer, 0, _tms_buffer_size);
detectChain(32);
}
Jtag::~Jtag()
{
free(_tms_buffer);
delete _jtag;
}
int Jtag::detectChain(unsigned max_dev)
{
char message[256];
uint8_t rx_buff[4];
/* WA for CH552/tangNano: write is always mandatory */
const uint8_t tx_buff[4] = {0xff, 0xff, 0xff, 0xff};
uint32_t tmp;
/* cleanup */
_devices_list.clear();
_irlength_list.clear();
_ir_bits_before = _ir_bits_after = _dr_bits_before = _dr_bits_after = 0;
go_test_logic_reset();
set_state(SHIFT_DR);
if (_verbose)
printInfo("Raw IDCODE:");
for (unsigned i = 0; i < max_dev; ++i) {
read_write(tx_buff, rx_buff, 32, 0);
tmp = 0;
for (int ii = 0; ii < 4; ++ii)
tmp |= (rx_buff[ii] << (8 * ii));
if (_verbose) {
snprintf(message, sizeof(message), "- %d -> 0x%08x", i, tmp);
printInfo(message);
}
if (tmp == 0) {
throw std::runtime_error("TDO is stuck at 0");
}
if (tmp == 0xffffffff) {
if (_verbose) {
snprintf(message, sizeof(message), "Fetched TDI, end-of-chain");
printInfo(message);
}
break;
}
/* search IDCODE in fpga_list and misc_dev_list
* since most device have idcode with high nibble masked
* we start to search sub IDCODE
* if IDCODE has no match: try the same with version unmasked
*/
bool found = false;
/* ckeck highest nibble to prevent confusion between Cologne Chip
* GateMate and Efinix Trion T4/T8 devices
*/
if (tmp == 0x20000001)
found = search_and_insert_device_with_idcode(tmp);
if (!found) /* not specific case -> search for full */
found = search_and_insert_device_with_idcode(tmp);
if (!found) /* if full idcode not found -> search for masked */
found = search_and_insert_device_with_idcode(tmp & 0x0fffffff);
if (!found) {
uint16_t mfg = IDCODE2MANUFACTURERID(tmp);
uint8_t part = IDCODE2PART(tmp);
uint8_t vers = IDCODE2VERS(tmp);
char error[1024];
snprintf(error, sizeof(error),
"Unknown device with IDCODE: 0x%08x"
" (manufacturer: 0x%03x (%s),"
" part: 0x%02x vers: 0x%x", tmp,
mfg, list_manufacturer[mfg].c_str(), part, vers);
throw std::runtime_error(error);
}
}
set_state(TEST_LOGIC_RESET);
flushTMS(true);
return _devices_list.size();
}
bool Jtag::search_and_insert_device_with_idcode(uint32_t idcode)
{
int irlength = -1;
auto dev = fpga_list.find(idcode);
if (dev != fpga_list.end())
irlength = dev->second.irlength;
if (irlength == -1) {
auto misc = misc_dev_list.find(idcode);
if (misc != misc_dev_list.end())
irlength = misc->second.irlength;
}
if (irlength == -1) {
auto misc = this->_user_misc_devs.find(idcode);
if (misc != this->_user_misc_devs.end())
irlength = misc->second.irlength;
}
if (irlength == -1)
return false;
return insert_first(idcode, irlength);
}
bool Jtag::insert_first(uint32_t device_id, uint16_t irlength)
{
_devices_list.insert(_devices_list.begin(), device_id);
_irlength_list.insert(_irlength_list.begin(), irlength);
return true;
}
int Jtag::device_select(unsigned index)
{
if (index > _devices_list.size())
return -1;
device_index = index;
/* get number of devices, in the JTAG chain,
* before the selected one
*/
_dr_bits_before = _devices_list.size() - device_index - 1;
/* get number of devices in the JTAG chain
* after the selected one
*/
_dr_bits_after = device_index;
_dr_bits = vector<uint8_t>((std::max(_dr_bits_after, _dr_bits_before) + 7)/8, 0);
/* when the device is not alone and not
* the first a serie of bypass must be
* send to complete send ir sequence
*/
_ir_bits_after = 0;
for (int i = 0; i < device_index; ++i)
_ir_bits_after += _irlength_list[i];
/* send serie of bypass instructions
* final size depends on number of device
* before targeted and irlength of each one
*/
_ir_bits_before = 0;
for (unsigned i = device_index + 1; i < _devices_list.size(); ++i)
_ir_bits_before += _irlength_list[i];
_ir_bits = vector<uint8_t>((std::max(_ir_bits_before, _ir_bits_after) + 7) / 8, 0xff); // BYPASS command is all-ones
return device_index;
}
void Jtag::setTMS(unsigned char tms)
{
display("%s %x %d %d\n", __func__, tms, _num_tms, (_num_tms >> 3));
if (_num_tms+1 == _tms_buffer_size * 8)
flushTMS(false);
if (tms != 0)
_tms_buffer[_num_tms>>3] |= (0x1) << (_num_tms & 0x7);
_num_tms++;
}
/* reconstruct byte sent to TMS pins
* - use up to 6 bits
* -since next bit after length is use to
* fix TMS state after sent we copy last bit
* to bit after next
* -bit 7 is TDI state for each clk cycles
*/
int Jtag::flushTMS(bool flush_buffer)
{
int ret = 0;
if (_num_tms != 0) {
display("%s: %d %x\n", __func__, _num_tms, _tms_buffer[0]);
ret = _jtag->writeTMS(_tms_buffer, _num_tms, flush_buffer, _curr_tdi);
/* reset buffer and number of bits */
memset(_tms_buffer, 0, _tms_buffer_size);
_num_tms = 0;
} else if (flush_buffer) {
_jtag->flush();
}
return ret;
}
void Jtag::go_test_logic_reset()
{
/* independently to current state 5 clk with TMS high is enough */
for (int i = 0; i < 6; i++)
setTMS(0x01);
flushTMS(false);
_state = TEST_LOGIC_RESET;
}
int Jtag::read_write(const uint8_t *tdi, unsigned char *tdo, int len, char last)
{
flushTMS(false);
_jtag->writeTDI(tdi, tdo, len, last);
if (last == 1)
_state = (_state == SHIFT_DR) ? EXIT1_DR : EXIT1_IR;
return 0;
}
void Jtag::toggleClk(int nb, uint8_t tdi)
{
unsigned char c = (TEST_LOGIC_RESET == _state) ? 1 : 0;
flushTMS(false);
if (_jtag->toggleClk(c, tdi, nb) >= 0)
return;
throw std::exception();
return;
}
int Jtag::shiftDR(const uint8_t *tdi, unsigned char *tdo, int drlen, tapState_t end_state)
{
/* if current state not shift DR
* move to this state
*/
if (_state != SHIFT_DR) {
set_state(SHIFT_DR);
flushTMS(false); // force transmit tms state
if (_dr_bits_before)
read_write(_dr_bits.data(), NULL, _dr_bits_before, false);
}
/* write tdi (and read tdo) to the selected device
* end (ie TMS high) is used only when current device
* is the last of the chain and a state change must
* be done
*/
read_write(tdi, tdo, drlen, _dr_bits_after == 0 && end_state != SHIFT_DR);
/* if it's asked to move in FSM */
if (end_state != SHIFT_DR) {
/* if current device is not the last */
if (_dr_bits_after)
read_write(_dr_bits.data(), NULL, _dr_bits_after, true); // its the last force
// tms high with last bit
/* move to end_state */
set_state(end_state);
}
return 0;
}
int Jtag::shiftIR(unsigned char tdi, int irlen, tapState_t end_state)
{
if (irlen > 8) {
cerr << "Error: this method this direct char don't support more than 1 byte" << endl;
return -1;
}
return shiftIR(&tdi, NULL, irlen, end_state);
}
int Jtag::shiftIR(unsigned char *tdi, unsigned char *tdo, int irlen, tapState_t end_state)
{
display("%s: avant shiftIR\n", __func__);
/* if not in SHIFT IR move to this state */
if (_state != SHIFT_IR) {
set_state(SHIFT_IR);
if (_ir_bits_before)
read_write(_ir_bits.data(), NULL, _ir_bits_before, false);
}
display("%s: envoi ircode\n", __func__);
/* write tdi (and read tdo) to the selected device
* end (ie TMS high) is used only when current device
* is the last of the chain and a state change must
* be done
*/
read_write(tdi, tdo, irlen, _ir_bits_after == 0 && end_state != SHIFT_IR);
/* it's asked to move out of SHIFT IR state */
if (end_state != SHIFT_IR) {
/* again if devices after fill '1' */
if (_ir_bits_after > 0)
read_write(_ir_bits.data(), NULL, _ir_bits_after, true);
/* move to the requested state */
set_state(end_state);
}
return 0;
}
void Jtag::set_state(tapState_t newState, const uint8_t tdi)
{
_curr_tdi = tdi;
unsigned char tms = 0;
while (newState != _state) {
display("_state : %16s(%02d) -> %s(%02d) ",
getStateName((tapState_t)_state),
_state,
getStateName((tapState_t)newState), newState);
switch (_state) {
case TEST_LOGIC_RESET:
if (newState == TEST_LOGIC_RESET) {
tms = 1;
} else {
tms = 0;
_state = RUN_TEST_IDLE;
}
break;
case RUN_TEST_IDLE:
if (newState == RUN_TEST_IDLE) {
tms = 0;
} else {
tms = 1;
_state = SELECT_DR_SCAN;
}
break;
case SELECT_DR_SCAN:
switch (newState) {
case CAPTURE_DR:
case SHIFT_DR:
case EXIT1_DR:
case PAUSE_DR:
case EXIT2_DR:
case UPDATE_DR:
tms = 0;
_state = CAPTURE_DR;
break;
default:
tms = 1;
_state = SELECT_IR_SCAN;
}
break;
case SELECT_IR_SCAN:
switch (newState) {
case CAPTURE_IR:
case SHIFT_IR:
case EXIT1_IR:
case PAUSE_IR:
case EXIT2_IR:
case UPDATE_IR:
tms = 0;
_state = CAPTURE_IR;
break;
default:
tms = 1;
_state = TEST_LOGIC_RESET;
}
break;
/* DR column */
case CAPTURE_DR:
if (newState == SHIFT_DR) {
tms = 0;
_state = SHIFT_DR;
} else {
tms = 1;
_state = EXIT1_DR;
}
break;
case SHIFT_DR:
if (newState == SHIFT_DR) {
tms = 0;
} else {
tms = 1;
_state = EXIT1_DR;
}
break;
case EXIT1_DR:
switch (newState) {
case PAUSE_DR:
case EXIT2_DR:
case SHIFT_DR:
case EXIT1_DR:
tms = 0;
_state = PAUSE_DR;
break;
default:
tms = 1;
_state = UPDATE_DR;
}
break;
case PAUSE_DR:
if (newState == PAUSE_DR) {
tms = 0;
} else {
tms = 1;
_state = EXIT2_DR;
}
break;
case EXIT2_DR:
switch (newState) {
case SHIFT_DR:
case EXIT1_DR:
case PAUSE_DR:
tms = 0;
_state = SHIFT_DR;
break;
default:
tms = 1;
_state = UPDATE_DR;
}
break;
case UPDATE_DR:
case UPDATE_IR:
if (newState == RUN_TEST_IDLE) {
tms = 0;
_state = RUN_TEST_IDLE;
} else {
tms = 1;
_state = SELECT_DR_SCAN;
}
break;
/* IR column */
case CAPTURE_IR:
if (newState == SHIFT_IR) {
tms = 0;
_state = SHIFT_IR;
} else {
tms = 1;
_state = EXIT1_IR;
}
break;
case SHIFT_IR:
if (newState == SHIFT_IR) {
tms = 0;
} else {
tms = 1;
_state = EXIT1_IR;
}
break;
case EXIT1_IR:
switch (newState) {
case PAUSE_IR:
case EXIT2_IR:
case SHIFT_IR:
case EXIT1_IR:
tms = 0;
_state = PAUSE_IR;
break;
default:
tms = 1;
_state = UPDATE_IR;
}
break;
case PAUSE_IR:
if (newState == PAUSE_IR) {
tms = 0;
} else {
tms = 1;
_state = EXIT2_IR;
}
break;
case EXIT2_IR:
switch (newState) {
case SHIFT_IR:
case EXIT1_IR:
case PAUSE_IR:
tms = 0;
_state = SHIFT_IR;
break;
default:
tms = 1;
_state = UPDATE_IR;
}
break;
case UNKNOWN:;
// UNKNOWN should not be valid...
throw std::exception();
}
setTMS(tms);
display("%d %d %d %x\n", tms, _num_tms-1, _state,
_tms_buffer[(_num_tms-1) / 8]);
}
/* force write buffer */
flushTMS(false);
}
const char *Jtag::getStateName(tapState_t s)
{
switch (s) {
case TEST_LOGIC_RESET:
return "TEST_LOGIC_RESET";
case RUN_TEST_IDLE:
return "RUN_TEST_IDLE";
case SELECT_DR_SCAN:
return "SELECT_DR_SCAN";
case CAPTURE_DR:
return "CAPTURE_DR";
case SHIFT_DR:
return "SHIFT_DR";
case EXIT1_DR:
return "EXIT1_DR";
case PAUSE_DR:
return "PAUSE_DR";
case EXIT2_DR:
return "EXIT2_DR";
case UPDATE_DR:
return "UPDATE_DR";
case SELECT_IR_SCAN:
return "SELECT_IR_SCAN";
case CAPTURE_IR:
return "CAPTURE_IR";
case SHIFT_IR:
return "SHIFT_IR";
case EXIT1_IR:
return "EXIT1_IR";
case PAUSE_IR:
return "PAUSE_IR";
case EXIT2_IR:
return "EXIT2_IR";
case UPDATE_IR:
return "UPDATE_IR";
default:
return "Unknown";
}
}