// SPDX-License-Identifier: Apache-2.0 /* * Copyright (C) 2020 Gwenhael Goavec-Merou */ #include #include #include #include #include #include #include #include #include "display.hpp" #include "ftdiJtagMPSSE.hpp" #include "ftdipp_mpsse.hpp" using namespace std; #define DEBUG 0 #ifdef DEBUG #define display(...) \ do { \ if (_verbose) fprintf(stdout, __VA_ARGS__); \ }while(0) #else #define display(...) do {}while(0) #endif FtdiJtagMPSSE::FtdiJtagMPSSE(const cable_t &cable, const string &dev, const string &serial, uint32_t clkHZ, bool invert_read_edge, int8_t verbose): FTDIpp_MPSSE(cable, dev, serial, clkHZ, verbose), _ch552WA(false), _write_mode(MPSSE_WRITE_NEG), // always write on neg edge _read_mode(0), _invert_read_edge(invert_read_edge), // false: pos, true: neg _tdo_pos(0) { init_internal(cable.config); } FtdiJtagMPSSE::~FtdiJtagMPSSE() { int read; /* Before shutdown, we must wait until everything is shifted out * Do this by temporary enabling loopback mode, write something * and wait until we can read it back */ static unsigned char tbuf[16] = { SET_BITS_LOW, 0xff, 0x00, SET_BITS_HIGH, 0xff, 0x00, LOOPBACK_START, static_cast(MPSSE_DO_READ | _read_mode | MPSSE_DO_WRITE | _write_mode | MPSSE_LSB), 0x04, 0x00, 0xaa, 0x55, 0x00, 0xff, 0xaa, LOOPBACK_END }; mpsse_store(tbuf, 16); read = mpsse_read(tbuf, 5); if (read != 5) fprintf(stderr, "Loopback failed, expect problems on later runs %d\n", read); } void FtdiJtagMPSSE::init_internal(const mpsse_bit_config &cable) { display("iProduct : %s\n", _iproduct); if (!strncmp((const char *)_iproduct, "Sipeed-Debug", 12)) { _ch552WA = true; } display("%x\n", cable.bit_low_val); display("%x\n", cable.bit_low_dir); display("%x\n", cable.bit_high_val); display("%x\n", cable.bit_high_dir); if (init(5, 0xfb, BITMODE_MPSSE) != 0) throw std::runtime_error("low level FTDI init failed"); config_edge(); _curr_tms = (cable.bit_low_val >> 3) & 0x01; _curr_tdi = (cable.bit_low_val >> 1) & 0x01; } int FtdiJtagMPSSE::setClkFreq(uint32_t clkHZ) { int ret = FTDIpp_MPSSE::setClkFreq(clkHZ); config_edge(); return ret; } void FtdiJtagMPSSE::config_edge() { /* at high (>15MHz) with digilent cable (arty) * opposite edges must be used. * Not required with classic FT2232 but user selectable */ if (_invert_read_edge || (FTDIpp_MPSSE::getClkFreq() >= 15000000 && !strncmp((const char *)_iproduct, "Digilent USB Device", 19))) { _read_mode = MPSSE_READ_NEG; } else { _read_mode = 0; } } int FtdiJtagMPSSE::writeTMS(const uint8_t *tms, uint32_t len, bool flush_buffer, const uint8_t tdi) { (void) flush_buffer; display("%s %d %d\n", __func__, len, (len/8)+1); uint8_t curr_tdi = (tdi << 7); if (len == 0) return 0; int xfer = len; int iter = _buffer_size / 3; int offset = 0, pos = 0; uint8_t buf[3]= {static_cast(MPSSE_WRITE_TMS | MPSSE_LSB | MPSSE_BITMODE | _write_mode), 0, 0}; while (xfer > 0) { uint8_t curr_tms = 0; int bit_to_send = (xfer > 6) ? 6 : xfer; buf[1] = bit_to_send-1; buf[2] = curr_tdi; for (int i = 0; i < bit_to_send; i++, offset++) { curr_tms = ((tms[offset >> 3] & (1 << (offset & 0x07))) ? 1 : 0); buf[2] |= (curr_tms << i); } buf[2] |= (curr_tms << bit_to_send); pos+=3; mpsse_store(buf, 3); if (pos == iter * 3) { pos = 0; if (mpsse_write() < 0) printf("writeTMS: error\n"); if (_ch552WA) { uint8_t c[len/8+1]; int ret = ftdi_read_data(_ftdi, c, len/8+1); if (ret != 0) { printf("ret : %d\n", ret); } } } xfer -= bit_to_send; } if (flush_buffer) mpsse_write(); if (_ch552WA) { uint8_t c[len/8+1]; ftdi_read_data(_ftdi, c, len/8+1); } return len; } /* need a WA for ch552 */ int FtdiJtagMPSSE::toggleClk(uint8_t tms, uint8_t tdi, uint32_t clk_len) { (void) tdi; int ret; uint32_t len = clk_len; /* clk ouput without data xfer is only supported * with 2232H, 4242H & 232H */ if (_ftdi->type == TYPE_2232H || _ftdi->type == TYPE_4232H || _ftdi->type == TYPE_232H) { uint8_t buf[] = {static_cast(0x8f), 0, 0}; while (len) { unsigned int chunk = len; if (chunk > 0x10000 * 8) chunk = 0x10000 * 8; if (chunk > 8) { unsigned cycles8 = chunk / 8; len -= cycles8 * 8; cycles8 --; buf[1] = ((cycles8) ) & 0xff; buf[2] = ((cycles8) >> 8) & 0xff; mpsse_store(buf, 3); } if (len && len < 9) { buf[0] = 0x8E; buf[1] = len - 1; mpsse_store(buf, 2); len = 0; } } ret = clk_len; } else { int byteLen = (len+7)/8; uint8_t buf_tms[byteLen]; memset(buf_tms, (tms) ? 0xff : 0x00, byteLen); ret = writeTMS(buf_tms, len, false); } return ret; } int FtdiJtagMPSSE::flush() { return mpsse_write(); } int FtdiJtagMPSSE::writeTDI(const uint8_t *tdi, uint8_t *tdo, uint32_t len, bool last) { /* 3 possible case : * - n * 8bits to send -> use byte command * - less than 8bits -> use bit command * - last bit to send -> sent in conjunction with TMS */ int tx_buff_size = mpsse_get_buffer_size(); int real_len = (last) ? len - 1 : len; // if its a buffer in a big send send len // else suppress last bit -> with TMS int nb_byte = real_len >> 3; // number of byte to send int nb_bit = (real_len & 0x07); // residual bits int xfer = tx_buff_size - 3; unsigned char c[xfer]; unsigned char *rx_ptr = (unsigned char *)tdo; unsigned char *tx_ptr = (unsigned char *)tdi; unsigned char tx_buf[3] = {(unsigned char)(MPSSE_LSB | ((tdi) ? (MPSSE_DO_WRITE | _write_mode) : 0) | ((tdo) ? (MPSSE_DO_READ | _read_mode) : 0)), static_cast((xfer - 1) & 0xff), // low static_cast((((xfer - 1) >> 8) & 0xff))}; // high display("%s len : %d %d %d %d last: %d\n", __func__, len, real_len, nb_byte, nb_bit, last); if ((nb_byte + _num + 3) > _buffer_size) mpsse_write(); if ((nb_byte * 8) + nb_bit != real_len) { printf("pas cool\n"); throw std::exception(); } /* if only one full byte use BITMODE to reduce * transaction size */ if (nb_byte == 1 && nb_bit == 0) { nb_byte = 0; nb_bit = 8; } while (nb_byte != 0) { int xfer_len = (nb_byte > xfer) ? xfer : nb_byte; tx_buf[1] = (((xfer_len - 1) ) & 0xff); // low tx_buf[2] = (((xfer_len - 1) >> 8) & 0xff); // high mpsse_store(tx_buf, 3); if (tdi) { mpsse_store(tx_ptr, xfer_len); tx_ptr += xfer_len; } if (tdo) { mpsse_read(rx_ptr, xfer_len); rx_ptr += xfer_len; } else if (_ch552WA) { mpsse_write(); ftdi_read_data(_ftdi, c, xfer_len); } else if (!last) { mpsse_write(); } nb_byte -= xfer_len; } unsigned char last_bit = (tdi) ? *tx_ptr : 0; // never double write when nb_bit == 0 bool double_write = (nb_bit != 0) ? true : false; if (nb_bit != 0) { display("%s read/write %d bit\n", __func__, nb_bit); tx_buf[0] |= MPSSE_BITMODE; tx_buf[1] = nb_bit - 1; mpsse_store(tx_buf, 2); if (tdi) { display("%s last_bit %x size %d\n", __func__, last_bit, nb_bit-1); mpsse_store(last_bit); } if (tdo && !last) { mpsse_read(rx_ptr, 1); double_write = false; /* realign we have read nb_bit * since LSB add bit by the left and shift * we need to complete shift */ *rx_ptr >>= (8 - nb_bit); display("%s %x\n", __func__, *rx_ptr); } else if (_ch552WA) { if (tdo) { mpsse_read(rx_ptr, 1); double_write = false; *rx_ptr >>= (8 - nb_bit); } else { mpsse_write(); ftdi_read_data(_ftdi, c, nb_bit); } } else if (!last) { mpsse_write(); } } /* display : must be dropped */ if (_verbose && tdo) { display("\n"); for (int i = (len / 8) - 1; i >= 0; i--) display("%x ", (unsigned char)tdo[i]); display("\n"); } if (last == 1) { last_bit = (tdi)? (*tx_ptr & (1 << nb_bit)) : 0; display("%s move to EXIT1_xx and send last bit %x\n", __func__, (last_bit?0x81:0x01)); /* write the last bit in conjunction with TMS */ tx_buf[0] = MPSSE_WRITE_TMS | MPSSE_LSB | MPSSE_BITMODE | _write_mode | ((tdo) ? (MPSSE_DO_READ | _read_mode) : 0); tx_buf[1] = 0x0; // send 1bit tx_buf[2] = ((last_bit) ? 0x81 : 0x01); // we know in TMS tdi is bit 7 // and to move to EXIT_XR TMS = 1 mpsse_store(tx_buf, 3); if (tdo) { unsigned char c[2]; int index = 0; mpsse_read(c, 1 + ((double_write)?1:0)); if (double_write) { *rx_ptr = c[index] >> (8-nb_bit); index++; } /* in this case for 1 one it's always bit 7 */ *rx_ptr |= (((c[index]) & 0x80) >> (7 - nb_bit)); } else if (_ch552WA) { mpsse_write(); ftdi_read_data(_ftdi, c, 1); } else { mpsse_write(); } } return 0; } int32_t FtdiJtagMPSSE::update_tms_buff(uint8_t *buffer, uint8_t bit, uint32_t offset, uint8_t tdi, uint8_t *tdo, bool end) { int32_t ret; if (_verbose) printf("%s %d %02x %d\n", __func__, offset, buffer[0], end); if (!end) { uint8_t bit_shift = (1 << (offset)); if (bit) buffer[0] |= bit_shift; else buffer[0] &= ~bit_shift; offset++; } if (offset == 6 || end) { if (tdi) buffer[0] |= (0x01 << 7); else buffer[0] &= ~(0x01 << 7); uint8_t mp[3] = { static_cast(MPSSE_WRITE_TMS | MPSSE_LSB | MPSSE_BITMODE | _write_mode | MPSSE_DO_READ | _read_mode ), static_cast(offset - 1), buffer[0] }; // force a write if (_verbose) printf("\t%02x %02d %02x\n", mp[0], mp[1], mp[2]); if ((ret = mpsse_store(mp, 3)) < 0) return ret; uint8_t tdo_tmp; if ((ret = mpsse_read(&tdo_tmp, 1)) < 0) return ret; update_tdo_buff(&tdo_tmp, tdo, offset); offset = 0; buffer[0] = 0; } return offset; } uint32_t FtdiJtagMPSSE::update_tdo_buff(uint8_t *buffer, uint8_t *tdo, uint32_t len) { if (_verbose) { printError("update tdo " + std::to_string(_tdo_pos) + " " + std::to_string(len) + " ", false); uint32_t tt = (len + 7) / 8; for (uint32_t i = 0; i < tt; i++) printf("%02x ", buffer[i]); } for (uint32_t i = 0; i < len; i++, _tdo_pos++) { uint8_t bit = (buffer[i >> 3] >> (i & 0x07)) & 0x01; uint8_t mask = 1 << (_tdo_pos & 0x07); if (bit) tdo[_tdo_pos >> 3] |= mask; else tdo[_tdo_pos >> 3] &= ~mask; } if (_verbose) printf("\n"); return _tdo_pos; } bool FtdiJtagMPSSE::writeTMSTDI(const uint8_t *tms, const uint8_t *tdi, uint8_t *tdo, uint32_t len) { int32_t ret; uint32_t max_len = 1024; uint8_t mode = 0; // current state: 0 none, 1 TDI, 2 TMS uint8_t tdi_buf[max_len]; // buffer to store TDI sequence uint8_t tms_tmp = 0; // buffer to store TMS sequence (limited to 6bits per cmd) uint8_t tdo_tmp[max_len]; // local TDO sequence uint32_t buff_len = 0; // current bits stored memset(tdi_buf, 0, max_len); memset(tdo_tmp, 0, max_len); _tdo_pos = 0; // current bits read if (_verbose) printSuccess("begin: " + std::to_string(len)); for (uint32_t buf_pos = 0; buf_pos < len; buf_pos++) { /* extract bit from TMS and TDI sequence */ uint8_t tms_bit = (tms[buf_pos >> 3] >> (buf_pos & 0x07) & 0x01); uint8_t tdi_bit = (tdi[buf_pos >> 3] >> (buf_pos & 0x07) & 0x01); if (_verbose) { char mess[256]; snprintf(mess, 256, "tms %d -> %d tdi %d -> %d mode %d %d/%d (%d)", _curr_tms, tms_bit, _curr_tdi, tdi_bit, mode, buf_pos, len, buff_len); printInfo(mess); } /* possible case: * tdi & tms not changed -> write tdi * tdi change but tms not -> write tdi * tdi idem but tms changed -> write tms * tdi & tms changed -> serie of write tms * but finally: * if tms is not changed -> write tdi * otherwise -> write tms */ /* tms unchanged -> try to use TDI/TDO transaction */ if (tms_bit == _curr_tms) { /* a tms transaction exist but not flushed or full * if tdi is not changed -> update tms sequence */ if (mode == 2 && buff_len != 0 && tdi_bit == _curr_tdi) { ret = update_tms_buff(&tms_tmp, tms_bit, buff_len, tdi_bit, tdo); if (ret < 0) return false; else buff_len = ret; } else { /* tdi sequence * first flush tms sequence if required */ if (mode != 1 && buff_len != 0) { ret = update_tms_buff(&tms_tmp, 0, buff_len, _curr_tdi, tdo, true); if (ret < 0) return false; else buff_len = ret; } /* update tdi buffer with one more bit */ uint8_t mask = (1 << (buff_len & 0x07)); if (tdi_bit) tdi_buf[buff_len >> 3] |= mask; else tdi_buf[buff_len >> 3] &= ~mask; buff_len++; mode = 1; } /* TMS is changed -> TMS transaction */ } else { /* flush */ /* previous write TDI -> flush */ if (mode == 1 && buff_len > 0) { bool is_end = false; /* tms 0 -> 1: it's handled by writeTDI: * append bit to avoid another transaction */ if (_curr_tms == 0 && tms_bit == 1) { uint8_t mask = (1 << (buff_len & 0x07)); if (tdi_bit) tdi_buf[buff_len >> 3] |= mask; else tdi_buf[buff_len >> 3] &= ~mask; buff_len++; is_end = true; } writeTDI(tdi_buf, tdo_tmp, buff_len, is_end); update_tdo_buff(tdo_tmp, tdo, buff_len); memset(tdi_buf, 0, max_len); buff_len = 0; if (is_end) { _curr_tdi = tdi_bit; mode = 1; continue; } /* same state -> simply flush */ } else if (tdi_bit != _curr_tdi && mode == 2 && buff_len > 0) { ret = update_tms_buff(&tms_tmp, 0, buff_len, _curr_tdi, tdo, true); if (ret < 0) return false; else buff_len = ret; tms_tmp = 0; } /* update */ ret = update_tms_buff(&tms_tmp, tms_bit, buff_len, tdi_bit, tdo); if (ret < 0) return false; else buff_len = ret; mode = 2; } /* buffer full? */ if (buff_len == 8*max_len && mode == 1) { writeTDI(tdi_buf, tdo_tmp, buff_len, false); update_tdo_buff(tdo_tmp, tdo, buff_len); memset(tdi_buf, 0, max_len); buff_len = 0; } else if (buff_len == 6 && mode == 2) { ret = update_tms_buff(&tms_tmp, 0, buff_len, _curr_tdi, tdo, true); if (ret < 0) return false; else buff_len = ret; tms_tmp = 0; } _curr_tdi = tdi_bit; _curr_tms = tms_bit; } /* end -> force flush buffers */ if (buff_len > 0) { switch (mode) { case 1: writeTDI(tdi_buf, tdo_tmp, buff_len, false); update_tdo_buff(tdo_tmp, tdo, buff_len); break; case 2: if (update_tms_buff(&tms_tmp, 0, buff_len, _curr_tdi, tdo, true) < 0) return false; break; } } if (_verbose) { printSuccess("end state: tdi " + std::to_string(_curr_tdi) + " tms " + std::to_string(_curr_tms)); } return true; }