diff --git a/CMakeLists.txt b/CMakeLists.txt index 65cbd91..b096f6d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,6 +95,7 @@ set(OPENFPGALOADER_SOURCE src/latticeBitParser.cpp src/gowin.cpp src/device.cpp + src/jlink.cpp src/lattice.cpp src/progressBar.cpp src/fsparser.cpp @@ -129,6 +130,7 @@ set(OPENFPGALOADER_HEADERS src/bitparser.hpp src/ftdiJtagBitbang.hpp src/ftdiJtagMPSSE.hpp + src/jlink.hpp src/jtag.hpp src/jtagInterface.hpp src/fsparser.hpp diff --git a/doc/cable.yml b/doc/cable.yml index a168f5a..853fa91 100644 --- a/doc/cable.yml +++ b/doc/cable.yml @@ -172,6 +172,13 @@ ecpix5-debug: URL: https://shop.lambdaconcept.com/home/46-ecpix-5.html +jlink: + + - Name: jlink + Description: SEGGER J-Link Debug Probes + URL: https://www.segger.com/products/debug-probes/j-link + + jtag-smt2-nc: - Name: jtag-smt2-nc diff --git a/src/cable.hpp b/src/cable.hpp index 64bd2f7..e949171 100644 --- a/src/cable.hpp +++ b/src/cable.hpp @@ -19,6 +19,7 @@ enum communication_type { MODE_CH552_JTAG, /*! ch552_jtag firmware */ MODE_FTDI_BITBANG, /*! used with ft232RL/ft231x */ MODE_FTDI_SERIAL, /*! ft2232, ft232H */ + MODE_JLINK, /*! ft2232, ft232H */ MODE_DIRTYJTAG, /*! JTAG probe firmware for STM32F1 */ MODE_USBBLASTER, /*! JTAG probe firmware for USBBLASTER */ MODE_CMSISDAP, /*! CMSIS-DAP JTAG probe */ @@ -59,6 +60,7 @@ static std::map cable_list = { {"ft232RL", {MODE_FTDI_BITBANG, {0x0403, 0x6001, INTERFACE_A, 0x08, 0x0B, 0x08, 0x0B}}}, {"ft4232", {MODE_FTDI_SERIAL, {0x0403, 0x6011, INTERFACE_A, 0x08, 0x0B, 0x08, 0x0B}}}, {"ecpix5-debug", {MODE_FTDI_SERIAL, {0x0403, 0x6010, INTERFACE_A, 0xF8, 0xFB, 0xFF, 0xFF}}}, + {"jlink", {MODE_JLINK, {0x1366, 0x0105, 0, 0, 0, 0, 0 }}}, {"jtag-smt2-nc", {MODE_FTDI_SERIAL, {0x0403, 0x6014, INTERFACE_A, 0xe8, 0xeb, 0x00, 0x60}}}, {"orbtrace", {MODE_CMSISDAP, {0x1209, 0x3443, 0, 0, 0, 0, 0 }}}, {"tigard", {MODE_FTDI_SERIAL, {0x0403, 0x6010, INTERFACE_B, 0x08, 0x3B, 0x00, 0x00}}}, diff --git a/src/jlink.cpp b/src/jlink.cpp new file mode 100644 index 0000000..6dac2be --- /dev/null +++ b/src/jlink.cpp @@ -0,0 +1,730 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2021 Gwenhael Goavec-Merou + */ + +#include "jlink.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "display.hpp" + +#define VID 0x1366 +#define PID 0x0105 + +using namespace std; + +// convert 2Byte to 1 short +#define CONV_16B(_val) ((((uint16_t) _val[0]) << 0) | \ + (((uint16_t) _val[1]) << 8)) +// convert 4Byte to 1 word +#define CONV_32B(_val) ((((uint32_t) _val[0]) << 0) | \ + (((uint32_t) _val[1]) << 8) | \ + (((uint32_t) _val[2]) << 16) | \ + (((uint32_t) _val[3]) << 24)) +// a 0-byte is introduce when reading a packet with +// size multiple of 64 Byte but < 0x8000 +#define HAS_0BYTE(_len) ((_len != 0) && (_len % 64 == 0) && (_len != 0x8000)) + +// buffer capacity +#define BUF_SIZE 2048 + +Jlink::Jlink(uint32_t clkHz, int8_t verbose):_base_freq(0), _min_div(0), + jlink_write_ep(-1), jlink_read_ep(-1), jlink_interface(-1), + _verbose(verbose > 0), _debug(verbose > 1), _quiet(verbose < 0), + _num_bits(0), _last_tms(0), _last_tdi(0), + _hw_type(0), _major(0), _minor(0), _revision(0) +{ + // init libusb context + if (libusb_init(&jlink_ctx) < 0) + throw std::runtime_error("libusb init failed\n"); + + // search for all compatible devices + if (!jlink_scan_usb()) + throw std::runtime_error("can't find compatible device"); + + // get device capacity + if (!get_caps()) + throw std::runtime_error("can't read device CAPS"); + + // get hw version + if (get_hw_version() < 0) + throw std::runtime_error("can't read hw version"); + + get_speeds(); + + // configure device in JTAG mode + set_interface(0); + + // configure JTAG TCK frequency + setClkFreq(clkHz); + + if (!set_ks_power(true)) + throw std::runtime_error("can't set KS power"); +} + +Jlink::~Jlink() +{ + // flush buffers before quit + if (_num_bits != 0) + flush(); + // release interface + libusb_release_interface(jlink_handle, jlink_interface); + // close device + libusb_close(jlink_handle); + // context cleanup + libusb_exit(jlink_ctx); +} + +int Jlink::writeTMS(uint8_t *tms, uint32_t len, bool flush_buffer) +{ + // empty buffer + // if asked flush + if (len == 0) + return ((flush_buffer) ? flush() : 0); + + for (uint32_t pos = 0; pos < len; pos++) { + // buffer full -> write + if (_num_bits == BUF_SIZE * 8) { + // write + ll_write(NULL); + _num_bits = 0; + } + + _last_tms = (tms[pos >> 3] & (1 << (pos & 0x07))) ? 1 : 0; + + if (_last_tms) + _tms[(_num_bits >> 3)] |= (1 << (_num_bits & 0x07)); + else + _tms[(_num_bits >> 3)] &= ~(1 << (_num_bits & 0x07)); + if (_last_tdi) + _tdi[(_num_bits >> 3)] |= (1 << (_num_bits & 0x07)); + else + _tdi[(_num_bits >> 3)] &= ~(1 << (_num_bits & 0x07)); + _num_bits++; + } + + // flush where it's asked or if the buffer is full + if (flush_buffer || _num_bits == BUF_SIZE * 8) + return flush(); + return len; +} + +int Jlink::writeTDI(uint8_t *tx, uint8_t *rx, uint32_t len, bool end) +{ + if (len == 0) // nothing to do + return 0; + if (_num_bits != 0) // flush buffer to simplify next step + flush(); + + uint32_t xfer_len = BUF_SIZE * 8; // default to buffer capacity + uint8_t tms = (_last_tms) ? 0xff : 0x00; // set tms byte + uint8_t *tx_ptr = tx, *rx_ptr = rx; // use pointer to simplify algo + + /* write by burst */ + for (uint32_t rest = 0; rest < len; rest += xfer_len) { + if ((xfer_len + rest) > len) // len < buffer size + xfer_len = len - rest; // reduce xfer len + uint16_t tt = (xfer_len + 7) >> 3; // convert to Byte + memset(_tms, tms, tt); // fill tms buffer + memcpy(_tdi, tx_ptr, tt); // fill tdi buffer + _num_bits = xfer_len; // set buffer size in bit + if (end && xfer_len + rest == len) { // last sequence: set tms 1 + _last_tms = 1; + uint16_t idx = _num_bits - 1; + _tms[(idx >> 3)] |= (1 << (idx & 0x07)); + } + ll_write((rx) ? rx_ptr : NULL); // write + + tx_ptr += tt; + if (rx) + rx_ptr += tt; + } + + return len; +} + +// toggle clk with constant TDI and TMS. More or less same idea as writeTDI +int Jlink::toggleClk(uint8_t tms, uint8_t tdi, uint32_t clk_len) +{ + // nothing to do + if (clk_len == 0) + return 0; + if (_num_bits != 0) + flush(); + + _last_tms = tms; + _last_tdi = tdi; + uint8_t curr_tms = (tms) ? 0xff: 0x00; + uint8_t curr_tdi = (tdi) ? 0xff: 0x00; + + uint32_t len = clk_len; + + // flush buffer before starting + if (_num_bits != 0) + flush(); + + memset(_tdi, curr_tdi, BUF_SIZE); + memset(_tms, curr_tms, BUF_SIZE); + do { + _num_bits = BUF_SIZE * 8; + if (len < _num_bits) + _num_bits = len; + len -= _num_bits; + ll_write(NULL); + } while (len > 0); + + return clk_len; +} + +int Jlink::flush() +{ + return ll_write(NULL); +} + +bool Jlink::ll_write(uint8_t *tdo) +{ + if (_num_bits == 0) + return true; + uint32_t numbytes = (_num_bits + 7) >> 3; + uint8_t rx_buf[numbytes+2]; + uint8_t status; + // 1. cmd + dummy + numbits + tms + tdi + _xfer_buf[0] = EMU_CMD_HW_JTAG3; + _xfer_buf[1] = 0; // dummy + _xfer_buf[2] = static_cast((_num_bits >> 0) & 0xff); + _xfer_buf[3] = static_cast((_num_bits >> 8) & 0xff); + memcpy(_xfer_buf + 4, _tms, numbytes); + memcpy(_xfer_buf + 4 + numbytes, _tdi, numbytes); + + if (_debug) { + printf("Out : %u\n", numbytes); + printf("cmd : %02x\n", _xfer_buf[0]); + printf("dummy : %02x\n", _xfer_buf[1]); + printf("bitlength : %02x %02x (%u)\n", _xfer_buf[2], _xfer_buf[3], _num_bits); + printf("tms : "); + if (numbytes > 16) { + printf("snip"); + } else { + for (uint32_t i = 0; i < numbytes; i++) + printf("%02x ", _xfer_buf[i+4]); + } + printf("\n"); + printf("tdi : "); + if (numbytes > 16) { + printf("snip"); + } else { + for (uint32_t i = 0; i < numbytes; i++) + printf("%02x ", _xfer_buf[i+4+numbytes]); + } + printf("\n"); + printf("buffer : "); + for (uint32_t i = 0; i < 4 + (2 * numbytes); i++) + printf("%02x ", _xfer_buf[i]); + printf("\n"); + } + + if (!write_device(_xfer_buf, 4 + (2 * numbytes))) { + printError("fails to send buffer"); + throw std::runtime_error("fails to send buffer"); + } + + // 2. read tdo + status + int ret = read_device(rx_buf, numbytes+1); + if (ret < 0) { + printError("fails to read tdo"); + return false; + } + + // 3. read status + if ((uint32_t)ret == numbytes) { + printError("read status"); + if (!read_device(&status, 1)) { + printError("fails to read status\n"); + return false; + } + } else { + status = rx_buf[numbytes]; + } + + if (tdo) { + memcpy(tdo, rx_buf, numbytes); + + if (_debug) { + printf("tdo : "); + for (uint32_t i = 0; i < numbytes; i+=16) { + for (int ii = 0; ii < 16 && ((ii + i) < numbytes); ii++) + printf("%02x ", tdo[i+ii]); + printf("\n"); + } + } + } + if (_debug) + printf("\n"); + + _num_bits = 0; // clear counter + + return status == 0; +} + +bool Jlink::cmd_read(uint8_t cmd, uint8_t *val, int size) +{ + int actual_length; + int ret = libusb_bulk_transfer(jlink_handle, jlink_write_ep, + &cmd, 1, &actual_length, 5000); + if (ret < 0) { + printf("Error write cmd_read %d %s %s\n", ret, + libusb_error_name(ret), libusb_strerror(ret)); + return false; + } + + return (size == read_device(val, size)); +} + +bool Jlink::cmd_read(uint8_t cmd, uint16_t *val) +{ + if (!cmd_read(cmd, _xfer_buf, 2)) + return false; + + *val = CONV_16B(_xfer_buf); + + return true; +} + +bool Jlink::cmd_read(uint8_t cmd, uint32_t *val) +{ + if (!cmd_read(cmd, _xfer_buf, 4)) + return false; + + *val = CONV_32B(_xfer_buf); + + return true; +} + +bool Jlink::cmd_write(uint8_t cmd, uint16_t param) +{ + uint8_t tx_buf[3] = {cmd, + static_cast((param >> 0) & 0xff), + static_cast((param >> 8) & 0xff)}; + + int actual_length; + int ret = libusb_bulk_transfer(jlink_handle, jlink_write_ep, + tx_buf, 3, &actual_length, 5000); + if (ret < 0) { + printf("Error write cmd_write %d\n", ret); + printf("%s %s\n", libusb_error_name(ret), libusb_strerror(ret)); + return ret; + } + + return true; +} + +bool Jlink::cmd_write(uint8_t cmd, uint8_t param) +{ + uint8_t tx_buf[2] = {cmd, param}; + + int actual_length; + int ret = libusb_bulk_transfer(jlink_handle, jlink_write_ep, + tx_buf, 2, &actual_length, 5000); + if (ret < 0) { + printf("Error write cmd_write %d\n", ret); + printf("%s %s\n", libusb_error_name(ret), libusb_strerror(ret)); + return false; + } + + return true; +} + +int Jlink::read_device(uint8_t *buf, uint32_t size) +{ + int actual_length, tries = 3; + uint32_t recv = 0, rest = size; + uint8_t *rx_ptr = buf; + + do { + int ret = libusb_bulk_transfer(jlink_handle, jlink_read_ep, + rx_ptr, rest, &actual_length, 1000); + if (ret == 0) { + rx_ptr += actual_length; + recv += actual_length; + rest -= actual_length; + } else if (ret == LIBUSB_ERROR_TIMEOUT) { + tries--; + } else { + char toto[256]; + snprintf(toto, sizeof(toto), "Error read length %d %d %u %s %s\n", + ret, actual_length, size, libusb_error_name(ret), + libusb_strerror(ret)); + return ret; + } + } while (recv < size && tries != 0); + + if (tries == 0) + printError("fail"); + + return recv; +} + +bool Jlink::write_device(const uint8_t *buf, uint32_t size) +{ + int actual_length, tries = 4; + int rest_size = size, recv = 0; + uint8_t *buf_ptr = (uint8_t*)buf; + + do { + int ret = libusb_bulk_transfer(jlink_handle, jlink_write_ep, + (uint8_t *)buf_ptr, rest_size, &actual_length, + 1000); + if (ret == 0) { + rest_size -= actual_length; + buf_ptr += actual_length; + recv += actual_length; + } else if (ret == LIBUSB_ERROR_TIMEOUT) { + tries--; + } else { + printf("Error write %d\n", ret); + printf("%s %s\n", libusb_error_name(ret), + libusb_strerror(ret)); + return false; + } + } while (tries > 0 && rest_size > 0); + + if (tries == 0 && rest_size != 0) { + printf("error\n"); + return false; + } + + return ((uint32_t)recv == size); +} + +string Jlink::get_version() +{ + uint16_t length; + cmd_read(EMU_CMD_VERSION, &length); + uint8_t version[length]; + read_device(version, length); + return string(reinterpret_cast(version)); +} + +int Jlink::get_hw_version() +{ + if (!(_caps & EMU_CAP_GET_HW_VERSION)) { + printf("get hw version is not supported\n"); + printf("%u\n", _caps & EMU_CAP_GET_HW_VERSION); + return 0; + } + uint32_t version; + if (!cmd_read(EMU_CMD_GET_HW_VERSION, &version)) + return -1; + + _hw_type = (version / 1000000) % 100; + _major = (version / 10000) % 100; + _minor = (version / 100) % 100; + _revision = version % 100; + + if (_debug) + printf("%08x ", version); + if (!_quiet) { + printInfo("device type: " + jlink_hw_type[_hw_type] + + " major: " + std::to_string(_major) + + " minor: " + std::to_string(_minor) + + " revision: " + std::to_string(_revision)); + } + + return version; +} + +void Jlink::get_speeds() +{ + cmd_read(EMU_CMD_GET_SPEEDS, _xfer_buf, 6); + _base_freq = CONV_32B(_xfer_buf); + _min_div = CONV_16B((&_xfer_buf[4])); + + if (_debug) { + for (int i = 0; i < 6; i++) + printf("%02x ", _xfer_buf[i]); + printf("\n"); + + printf("%02x %04x\n", _base_freq, _min_div); + printf("%u %u\n", _base_freq, _min_div); + } +} + +int Jlink::setClkFreq(uint32_t clkHz) +{ + uint32_t max_freq = _base_freq / _min_div; + + if (clkHz > max_freq) { + printWarn("Jlink probe limited to " + + std::to_string(max_freq/1000) + "kHz"); + clkHz = max_freq; + } + + if (!cmd_write(EMU_CMD_SET_SPEED, static_cast(clkHz / 1000))) { + printError("setClkFreq: fail to configure frequency"); + return -EXIT_FAILURE; + } + + _clkHZ = clkHz; + return _clkHZ; +} + +bool Jlink::set_speed(uint16_t freqHz) +{ + uint16_t freqKHz = freqHz / 1000; + uint16_t max_speed = _base_freq / _min_div; + + if (freqKHz > max_speed) { + printf("max freq limited to %d\n", max_speed * 1000); + freqKHz = max_speed; + } + + return cmd_write(EMU_CMD_SET_SPEED, freqKHz); +} + +bool Jlink::get_caps() +{ + if (!cmd_read(EMU_CMD_GET_CAPS, &_caps)) + return false; + + if (_verbose) { + printf("%04x\n", _caps); + for (int i = 0; i < 32; i++) { + if ((_caps >> i) & 0x01) + printf("%2d %s\n", i, jlink_caps_flags[i].c_str()); + } + } + + return true; +} + +bool Jlink::get_result() +{ + uint8_t error_bit; + if (cmd_read(EMU_CMD_HW_JTAG_GET_RESULT, &error_bit, 1) != 1) { + printError("get result failed"); + return false; + } + printInfo("get_result " + std::to_string(error_bit)); + if (error_bit != 0) + printError("pas bon"); + return error_bit == 0; +} + +bool Jlink::set_ks_power(bool val) +{ + if (!cmd_write(EMU_CMD_SET_KS_POWER, static_cast((val) ? 1 : 0))) + return false; + return true; +} + +bool Jlink::max_mem_block(uint32_t *max_mem) +{ + if (!cmd_read(EMU_CMD_GET_MAX_MEM_BLOCK, max_mem)) + return false; + return true; +} + +// There is a typo in RM08001: +// select interface is 0 for JTAG and 1 for SWD +// so SubCmd must be 0..31 instead of 1..31 +bool Jlink::set_interface(uint8_t interface) +{ + uint8_t buf[2] = {EMU_CMD_SELECT_IF, interface}; + uint8_t res[4]; + write_device(buf, 2); + read_device(res, 4); + if (_debug) { + printf("set interface: "); + for (int i = 0; i < 4; i++) + printf("%02x ", res[i]); + printf("\n"); + } + return true; +} + +void Jlink::read_config() +{ + jlink_cfg_t cfg; + cmd_read(EMU_CMD_READ_CONFIG, reinterpret_cast(&cfg), 256); + + if (_verbose) { + printf("usb_adr : %02x\n", cfg.usb_adr); + printf("kickstart : %08x\n", cfg.kickstart); + printf("ip_address: %08x\n", cfg.ip_address); + printf("subnetmask: %08x\n", cfg.subnetmask); + printf("mac addr : "); + for (int i = 0; i < 6; i++) { + printf("%02x", (uint8_t)cfg.mackaddr[i]); + if (i < 5) + printf(":"); + } + printf("\n"); + } +} + +bool Jlink::write_data(const uint8_t *tms, const uint8_t *tdi, uint8_t *tdo, + uint16_t numbits) +{ + if (numbits > (BUF_SIZE * 8)) + numbits = BUF_SIZE * 8; + uint16_t numbytes = (numbits +7) >> 8; + + memcpy(_tms, tms, numbytes); + memcpy(_tdi, tdi, numbytes); + _num_bits = numbits; + return ll_write(tdo); +} + +bool Jlink::jlink_search_interface(libusb_device *dev, + libusb_device_descriptor *desc, int *interface_idx, + int *config_idx, int *alt_idx) +{ + bool found = false; + *interface_idx = -1; + *config_idx = -1; + /* 1. iterate on all interface */ + for (int cfg_idx = 0; cfg_idx < desc->bNumConfigurations; cfg_idx++) { + struct libusb_config_descriptor *cfg; + int ret = libusb_get_config_descriptor(dev, cfg_idx, &cfg); + if (ret != 0) { + printf("Fail to retrieve config_descriptor \n"); + return false; + } + + for (int if_idx=0; if_idx < cfg->bNumInterfaces; if_idx++) { + const struct libusb_interface *uif = &cfg->interface[if_idx]; + for (int intf_idx = 0; intf_idx < uif->num_altsetting; intf_idx++) { + const struct libusb_interface_descriptor *intf = &uif->altsetting[intf_idx]; + uint8_t intfClass = intf->bInterfaceClass; + uint8_t intfSubClass = intf->bInterfaceSubClass; + if (_debug) + printf("intfClass: %x intfSubClass: %x\n", intfClass, intfSubClass); + if (intfClass == 0xff && intfSubClass == 0xff) { + if (found) { + printError("too many compatible interface"); + return false; + } + found = true; + *interface_idx = if_idx; + *config_idx = cfg_idx; + *alt_idx = intf_idx; + } + } + if (_debug) + printf("%d\n", if_idx); + } + libusb_free_config_descriptor(cfg); + } + return true; +} + +bool Jlink::jlink_scan_usb() +{ + libusb_device **dev_list; + libusb_device *usb_dev; + libusb_device_handle *handle; + ssize_t list_size = libusb_get_device_list(jlink_ctx, &dev_list); + int i = 0; + + if (list_size == 0) + return false; + + while ((usb_dev = dev_list[i++]) != NULL) { + struct libusb_device_descriptor desc; + if (libusb_get_device_descriptor(usb_dev, &desc) != 0) { + printError("Unable to get device descriptor"); + return EXIT_FAILURE; + } + + if (desc.idProduct != PID || desc.idVendor != VID) + continue; + + if (_verbose) + printf("%04x:%04x (bus %d, device %2d)\n", + desc.idVendor, desc.idProduct, + libusb_get_bus_number(usb_dev), + libusb_get_device_address(usb_dev)); + /* try to open device to search for interface */ + int ret = libusb_open(usb_dev, &handle); + if (ret != 0) + return false; + int if_idx, cfg_idx, alt_idx; + if (jlink_search_interface(usb_dev, &desc, &if_idx, + &cfg_idx, &alt_idx)) { + jlink_devices_t dev; + dev.usb_dev = usb_dev; + dev.alt_idx = alt_idx; + dev.if_idx = if_idx; + dev.cfg_idx = cfg_idx; + device_available.push_back(dev); + } + libusb_close(handle); + } + libusb_free_device_list(dev_list, 1); + + // no JLINK probes found + if (device_available.size() == 0) { + printError("Error: no device found"); + return false; + } + + if (_debug) { + for (size_t d = 0; d < device_available.size(); d++) + printf("%x %x\n", device_available[d].if_idx, + device_available[d].cfg_idx); + } + + // more than one device plugged: TODO how to deal with that ? + if (device_available.size() > 1) { + printError("Error: to many devices"); + return false; + } + + // try to open JLINK device + int ret = libusb_open(device_available[0].usb_dev, &jlink_handle); + if (ret != 0) + return false; + + // request interface + jlink_interface = device_available[0].if_idx; + int cfg_idx = device_available[0].cfg_idx; + libusb_claim_interface(jlink_handle, jlink_interface); + + // search for IN and OUT endpoint + struct libusb_config_descriptor *cfg; + ret = libusb_get_config_descriptor(device_available[0].usb_dev, cfg_idx, &cfg); + if (ret != 0) { + printError("Can't get config descriptor"); + return false; + } + const struct libusb_interface *uif = &cfg->interface[jlink_interface]; + const struct libusb_interface_descriptor *intf = &uif->altsetting[cfg_idx]; + for (int i = 0; i < intf->bNumEndpoints; i++) { + struct libusb_endpoint_descriptor endpoint = intf->endpoint[i]; + if ((endpoint.bEndpointAddress & 0x80)) { + jlink_read_ep = endpoint.bEndpointAddress; + } else { + jlink_write_ep = endpoint.bEndpointAddress; + } + } + + libusb_free_config_descriptor(cfg); + + if (jlink_write_ep == -1 || jlink_read_ep == -1 || jlink_interface == -1) { + printError("error"); + return false; + } + + return true; +} diff --git a/src/jlink.hpp b/src/jlink.hpp new file mode 100644 index 0000000..c2710f4 --- /dev/null +++ b/src/jlink.hpp @@ -0,0 +1,311 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2021 Gwenhael Goavec-Merou + */ + +#ifndef SRC_JLINK_HPP_ +#define SRC_JLINK_HPP_ + +#include + +#include +#include + +#include "jtagInterface.hpp" + +/*! + * \brief Segger JLink probe driver + */ +class Jlink: public JtagInterface { + public: + /*! + * \brief contructor: open device + * \param[in] clkHz: output clock frequency + * \param[in] verbose: verbose level -1 quiet, 0 normal, + * 1 verbose, 2 debug + */ + Jlink(uint32_t clkHz, int8_t verbose); + + ~Jlink(); + + // 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 2048;} + bool isFull() override { return false;} + + // JLINK specifics methods + std::string get_version(); + void get_speeds(); + bool set_speed(uint16_t freqHz); + bool get_caps(); + bool set_ks_power(bool val); + void read_config(); + int get_hw_version(); + bool get_result(); + bool max_mem_block(uint32_t *max_mem); + + /*! + * \brief test method to access ll_write outer this class + * \param[in] tms: tms buffer + * \param[in] tdi: tdi buffer + * \param[out] tdo: tdo buffer + * \param[in] numbits: tms/tdi/tdo buffer size (in bit) + */ + bool write_data(const uint8_t *tms, const uint8_t *tdi, uint8_t *tdo, + uint16_t numbits); + + private: + // Jlink EMU_CMD code + enum { + EMU_CMD_VERSION = 0x01, + EMU_CMD_SET_SPEED = 0x05, + EMU_CMD_SET_KS_POWER = 0x08, + EMU_CMD_GET_SPEEDS = 0xC0, + EMU_CMD_GET_MAX_MEM_BLOCK = 0xD4, + EMU_CMD_HW_JTAG_GET_RESULT = 0xD6, + EMU_CMD_SELECT_IF = 0xC7, + EMU_CMD_HW_JTAG2 = 0xCE, + EMU_CMD_HW_JTAG3 = 0xCF, + EMU_CMD_GET_CAPS = 0xE8, + EMU_CMD_GET_HW_VERSION = 0xF0, + EMU_CMD_READ_CONFIG = 0xF2, + EMU_CMD_WRITE_CONFIG = 0xF3 + }; + + // JLink hardware type + const std::string jlink_hw_type[4] = { + "J-Link", + "J-Trace", + "Flasher", + "J-Link Pro" + }; + + // Jlink configuration structure + struct jlink_cfg_t { + uint8_t usb_adr; + uint8_t reserved1[3]; // 0x01 - 0x03: 0xff + uint32_t kickstart; // Kickstart power on JTAG-pin 19 + uint8_t reserved2[24]; // 0x08 - 0x1F: 0xff + uint32_t ip_address; // IP-Address (Only for J-Link Pro) + uint32_t subnetmask; // subnetmask (Only for J-Link Pro) + uint8_t reserved3[8]; // 0x08 - 0x1F: 0xff + uint8_t mackaddr[6]; // MAC-Address (Only for J-Link Pro) + uint8_t reserved[202]; // MAC-Address (Only for J-Link Pro) + } __attribute__((__packed__)); + typedef jlink_cfg_t jlink_cfg; + + // JLink caps code + typedef enum { + EMU_CAP_GET_HW_VERSION = (1 << 1), + EMU_CAP_READ_CONFIG = (1 << 4), + EMU_CAP_WRITE_CONFIG = (1 << 5), + EMU_CAP_SPEED_INFO = (1 << 9), + EMU_CAP_GET_HW_INFO = (1 << 12), + EMU_CAP_SELECT_IF = (1 << 17), + EMU_CAP_GET_CPU_CAPS = (1 << 21) + } emu_caps_t; + + // JLink caps code -> string + const std::string jlink_caps_flags[32] { + "EMU_CAP_RESERVED", + "EMU_CAP_GET_HW_VERSION", + "EMU_CAP_WRITE_DCC", + "EMU_CAP_ADAPTIVE_CLOCKING", + "EMU_CAP_READ_CONFIG", + "EMU_CAP_WRITE_CONFIG", + "EMU_CAP_TRACE", + "EMU_CAP_WRITE_MEM", + "EMU_CAP_READ_MEM", + "EMU_CAP_SPEED_INFO", + "EMU_CAP_EXEC_CODE", + "EMU_CAP_GET_MAX_BLOCK_SIZE", + "EMU_CAP_GET_HW_INFO", + "EMU_CAP_SET_KS_POWER", + "EMU_CAP_RESET_STOP_TIMED", + "Reserved", + "EMU_CAP_MEASURE_RTCK_REACT", + "EMU_CAP_SELECT_IF", // 17 + "EMU_CAP_RW_MEM_ARM79", + "EMU_CAP_GET_COUNTERS", + "EMU_CAP_READ_DCC", // 20 + "EMU_CAP_GET_CPU_CAPS", + "EMU_CAP_EXEC_CPU_CMD", + "EMU_CAP_SWO", + "EMU_CAP_WRITE_DCC_EX", + "EMU_CAP_UPDATE_FIRMWARE_EX", // 25 + "EMU_CAP_FILE_IO", + "EMU_CAP_REGISTER", + "EMU_CAP_INDICATORS", + "EMU_CAP_TEST_NET_SPEED", + "EMU_CAP_RAWTRACE", + "Reserved" + }; + + /*! + * \brief lowlevel write: EMU_CMD_HW_JTAGx implementation + * \param[out]: tdo: TDO read buffer (may be null) + * \return false when failure + */ + bool ll_write(uint8_t *tdo); + + /*! + * \brief read size Bytes using read endpoint + * \param[in] cmd: Jlink cmd + * \param[out] val: received Bytes + * \param[in] size: number of Bytes to read + * \return false when transaction failure, true otherwise + */ + bool cmd_read(uint8_t cmd, uint8_t *val, int size); + /*! + * \brief read one short using read endpoint + * \param[in] cmd: Jlink cmd + * \param[out] val: received short + * \return false when transaction failure, true otherwise + */ + bool cmd_read(uint8_t cmd, uint16_t *val); + /*! + * \brief read one word using read endpoint + * \param[in] cmd: Jlink cmd + * \param[out] val: received word + * \return false when transaction failure, true otherwise + */ + bool cmd_read(uint8_t cmd, uint32_t *val); + /*! + * \brief write one short using write endpoint + * \param[in] cmd: Jlink cmd + * \param[in] val: value to send + * \return false when transaction failure, true otherwise + */ + bool cmd_write(uint8_t cmd, uint16_t param); + /*! + * \brief write one Byte using write endpoint into EMU_CMD_X register + * \param[in] cmd: Jlink cmd + * \param[in] val: value to send + * \return false when transaction failure, true otherwise + */ + bool cmd_write(uint8_t cmd, uint8_t param); + + /*! + * \brief lowlevel method to read using libusb_bulk_transfer + * with read endpoint. If required do OByte read + * \param[out] buf: buffer used to store read Bytes + * \param[in] size: buffer size + * \return number of Byte reads or libusb error code + */ + int read_device(uint8_t *buf, uint32_t size); + /*! + * \brief lowlevel method to write using libusb_bulk_transfer + * with write endpoint. + * \param[in] buf: Bytes to send + * \param[in] size: buffer size + * \return false when failure, true otherwise + */ + bool write_device(const uint8_t *buf, uint32_t size); + + /*! + * \brief configure interface (JTAG/SWD) + * \param[in] interface: 0 -> JTAG, 1 -> SWD + */ + bool set_interface(uint8_t interface); + + /*! + * \brief analyze one USB peripheral to search if compatible and + * and for interface/config/alt IDs + * \param[in] dev: USB device + * \param[in] desc: libusb_device_descriptor + * \param[out] interface_idx: interface ID + * \param[out] config_idx: configuration descriptor ID + * \return false if failure or no interface found + */ + bool jlink_search_interface(libusb_device *dev, + libusb_device_descriptor *desc, + int *interface_idx, int *config_idx, int *alt_idx); + + /*! + * \brief iterate on all USB peripheral to find one JLink + * \return false when failure, unable to open or no device found + */ + bool jlink_scan_usb(); + + typedef struct { + libusb_device *usb_dev; + int if_idx; + int cfg_idx; + int alt_idx; + } jlink_devices_t; + + uint32_t _base_freq; /*!< JLink interface frequency */ + uint16_t _min_div; /*!> dividor applied to base freq */ + + int jlink_write_ep; /*!< jlink write endpoint */ + int jlink_read_ep; /*!< jlink read endpoint */ + int jlink_interface; /*!< jlink usb interface */ + libusb_device_handle *jlink_handle; + libusb_context *jlink_ctx; + std::vector device_available; /*!< list of compatible devices */ + bool _verbose; /*!< display informations */ + bool _debug; /*!< display debug messages */ + bool _quiet; /*!< no messages */ + + // buffers for xfer, tdi and tdo + // each jlink's buffer have 2K Byte + // enough to send full jtag write + // buffers must be independant + uint8_t _xfer_buf[(2048*2) + 4]; /*!> internal buffer */ + uint8_t _tms[2048]; /*!< TMS buffer */ + uint8_t _tdi[2048]; /*!< TDI buffer */ + uint32_t _num_bits; /*!< number of bits stored */ + uint32_t _last_tms; /*!< last known TMS state */ + uint32_t _last_tdi; /*!< last known TDI state */ + + uint32_t _caps; /*!< current probe capacity */ + uint8_t _hw_type; + uint8_t _major; /*!< major Jlink probe release number */ + uint8_t _minor; /*!< minor Jlink probe release number */ + uint8_t _revision; /*!< revision number */ +}; +#endif // SRC_JLINK_HPP_ diff --git a/src/jtag.cpp b/src/jtag.cpp index 1db3d38..6ae083c 100644 --- a/src/jtag.cpp +++ b/src/jtag.cpp @@ -21,6 +21,7 @@ #include "ftdipp_mpsse.hpp" #include "ftdiJtagBitbang.hpp" #include "ftdiJtagMPSSE.hpp" +#include "jlink.hpp" #ifdef ENABLE_CMSISDAP #include "cmsisDAP.hpp" #endif @@ -103,6 +104,9 @@ void Jtag::init_internal(cable_t &cable, const string &dev, const string &serial case MODE_DIRTYJTAG: _jtag = new DirtyJtag(clkHZ, _verbose); break; + case MODE_JLINK: + _jtag = new Jlink(clkHZ, _verbose); + break; case MODE_USBBLASTER: _jtag = new UsbBlaster(cable.config.vid, cable.config.pid, firmware_path, _verbose);