// SPDX-License-Identifier: Apache-2.0 /* * Copyright (C) 2020 Gwenhael Goavec-Merou */ #include #include #include #include #include #include #include #include #include "anlogicCable.hpp" #include "ch552_jtag.hpp" #include "display.hpp" #include "jtag.hpp" #include "ftdiJtagBitbang.hpp" #include "ftdiJtagMPSSE.hpp" #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 "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 /* * AD0 -> TCK * AD1 -> TDI * AD2 -> TD0 * 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): _verbose(verbose > 1), _state(RUN_TEST_IDLE), _tms_buffer_size(128), _num_tms(0), _board_name("nope"), device_index(0) { init_internal(cable, dev, serial, pin_conf, clkHZ, firmware_path, invert_read_edge, ip_adr, port); detectChain(5); } Jtag::~Jtag() { free(_tms_buffer); delete _jtag; } void Jtag::init_internal(const cable_t &cable, const string &dev, const string &serial, const jtag_pins_conf_t *pin_conf, uint32_t clkHZ, const string &firmware_path, const bool invert_read_edge, const string &ip_adr, int port) { switch (cable.type) { case MODE_ANLOGICCABLE: _jtag = new AnlogicCable(clkHZ); break; 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; case MODE_CH347: _jtag = new CH347Jtag(clkHZ, _verbose); break; case MODE_DIRTYJTAG: _jtag = new DirtyJtag(clkHZ, _verbose); break; case MODE_JLINK: _jtag = new Jlink(clkHZ, _verbose, cable.vid, cable.pid); 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); memset(_tms_buffer, 0, _tms_buffer_size); } int Jtag::detectChain(int max_dev) { char message[256]; unsigned char rx_buff[4]; /* WA for CH552/tangNano: write is always mandatory */ unsigned char tx_buff[4] = {0xff, 0xff, 0xff, 0xff}; unsigned int tmp; /* cleanup */ _devices_list.clear(); _irlength_list.clear(); go_test_logic_reset(); set_state(SHIFT_DR); if (_verbose) printInfo("Raw IDCODE:"); for (int i = 0; i < max_dev; i++) { read_write(tx_buff, rx_buff, 32, (i == max_dev-1)?1: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); } /* 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 */ if (tmp != 0 && tmp != 0xffffffff) { 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 & 0x0fffffff); if (!found) /* if masked not found -> search for full */ found = search_and_insert_device_with_idcode(tmp); 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); } } } go_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) 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; } uint16_t Jtag::device_select(uint16_t index) { if (index > (uint16_t) _devices_list.size()) return -1; device_index = index; 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); /* 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(unsigned char *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) { unsigned char c = (TEST_LOGIC_RESET == _state) ? 1 : 0; flushTMS(false); if (_jtag->toggleClk(c, 0, nb) >= 0) return; throw std::exception(); return; } int Jtag::shiftDR(unsigned char *tdi, unsigned char *tdo, int drlen, int end_state) { /* get number of devices in the JTAG chain * after the selected one */ int bits_after = device_index; /* if current state not shift DR * move to this state */ if (_state != SHIFT_DR) { set_state(SHIFT_DR); flushTMS(false); // force transmit tms state /* get number of devices, in the JTAG chain, * before the selected one */ int bits_before = _devices_list.size() - device_index - 1; /* if not 0 send enough bits */ if (bits_before > 0) { int n = (bits_before + 7) / 8; uint8_t tx[n]; memset(tx, 0xff, n); read_write(tx, NULL, bits_before, 0); } } /* 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, 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 (bits_after > 0) { int n = (bits_after + 7) / 8; uint8_t tx[n]; memset(tx, 0xff, n); read_write(tx, NULL, bits_after, 1); // 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, int 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, int end_state) { display("%s: avant shiftIR\n", __func__); int bypass_after = 0; if (end_state != SHIFT_IR) { /* when the device is not alone and not * the first a serie of bypass must be * send to complete send ir sequence */ for (int i = 0; i < device_index; i++) bypass_after += _irlength_list[i]; } /* if not in SHIFT IR move to this state */ if (_state != SHIFT_IR) { set_state(SHIFT_IR); /* force flush */ flushTMS(false); /* send serie of bypass instructions * final size depends on number of device * before targeted and irlength of each one */ int bypass_before = 0; for (unsigned int i = device_index + 1; i < _devices_list.size(); i++) bypass_before += _irlength_list[i]; /* if > 0 send bits */ if (bypass_before > 0) { int n = (bypass_before + 7) / 8; uint8_t tx[n]; memset(tx, 0xff, n); read_write(tx, NULL, bypass_before, 0); } } 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, bypass_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 (bypass_after > 0) { int n = (bypass_after + 7) / 8; uint8_t tx[n]; memset(tx, 0xff, n); read_write(tx, NULL, bypass_after, 1); } /* move to the requested state */ set_state(end_state); } return 0; } void Jtag::set_state(int newState) { unsigned char tms; 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: 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 UPDATE_IR: if (newState == RUN_TEST_IDLE) { tms = 0; _state = RUN_TEST_IDLE; } else { tms = 1; _state = SELECT_DR_SCAN; } break; } 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"; } }