diff --git a/99-openfpgaloader.rules b/99-openfpgaloader.rules index a3d066a..faac820 100644 --- a/99-openfpgaloader.rules +++ b/99-openfpgaloader.rules @@ -32,4 +32,7 @@ ATTRS{idVendor}=="09fb", ATTRS{idProduct}=="6010", MODE="664", GROUP="plugdev", # dirtyJTAG ATTRS{idVendor}=="1209", ATTRS{idProduct}=="c0ca", MODE="664", GROUP="plugdev", TAG+="uaccess" +# NXP ARM mbed +ATTRS{idVendor}=="0d28", ATTRS{idProduct}=="0204", MODE="664", GROUP="plugdev", TAG+="uaccess" + LABEL="openfpgaloader_rules_end" diff --git a/CMakeLists.txt b/CMakeLists.txt index b41229c..e55d7da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,7 @@ add_definitions(-DVERSION=\"v${PROJECT_VERSION}\") option(BUILD_STATIC "Whether or not to build with static libraries" OFF) option(ENABLE_UDEV "use udev to search JTAG adapter from /dev/xx" ON) +option(ENABLE_CMSISDAP "enable cmsis DAP interface (requires hidapi)" ON) option(USE_PKGCONFIG "Use pkgconfig to find libraries" ON) option(LINK_CMAKE_THREADS "Use CMake find_package to link the threading library" OFF) @@ -28,6 +29,7 @@ if(USE_PKGCONFIG) find_package(PkgConfig REQUIRED) pkg_check_modules(LIBFTDI REQUIRED libftdi1) pkg_check_modules(LIBUSB REQUIRED libusb-1.0) + pkg_check_modules(HIDAPI hidapi-libusb) if(ENABLE_UDEV) pkg_check_modules(LIBUDEV libudev) @@ -129,7 +131,6 @@ include_directories( ${LIBFTDI_INCLUDE_DIRS} ) - if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") find_library(LIBFTDI1STATIC libftdi1.a REQUIRED) find_library(LIBUSB1STATIC libusb-1.0.a REQUIRED) @@ -163,6 +164,20 @@ endif() if (BUILD_STATIC) set_target_properties(openFPGALoader PROPERTIES LINK_SEARCH_END_STATIC 1) endif() + +if (ENABLE_CMSISDAP) +if (HIDAPI_FOUND) + include_directories(${HIDAPI_INCLUDE_DIRS}) + target_link_libraries(openFPGALoader ${HIDAPI_LIBRARIES}) + add_definitions(-DENABLE_CMSISDAP=1) + target_sources(openFPGALoader PRIVATE src/cmsisDAP.cpp) + list (APPEND OPENFPGALOADER_HEADERS src/cmsisDAP.hpp) + message("cmsis_dap support enabled") +else() + message("hidapi-libusb not found: cmsis_dap support disabled") +endif() +endif(ENABLE_CMSISDAP) + endif() if (LINK_CMAKE_THREADS) diff --git a/README.md b/README.md index 4060557..57727bc 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ __Current supported kits:__ * [Digilent Basys3](https://reference.digilentinc.com/reference/programmable-logic/basys-3/start) (memory and spi flash) * Trenz cyc1000 Cyclone 10 LP 10CL025 (memory and spi flash) * [Colorlight 5A-75B (version 7)](https://fr.aliexpress.com/item/32281130824.html) (memory and spi flash) +* [Colorlight I5](https://www.colorlight-led.com/product/colorlight-i5-led-display-receiver-card.html) (memory and spi flash) * [Digilent Arty A7 xc7a35ti](https://reference.digilentinc.com/reference/programmable-logic/arty-a7/start) (memory and spi flash) * [Digilent Arty S7 xc7s50](https://reference.digilentinc.com/reference/programmable-logic/arty-s7/start) (memory and spi flash) * [Digilent Nexys Video xc7a200t](https://reference.digilentinc.com/reference/programmable-logic/nexys-video/start) (memory and spi flash) @@ -70,6 +71,7 @@ __Supported cables:__ * anlogic JTAG adapter * [digilent_hs2](https://store.digilentinc.com/jtag-hs2-programming-cable/): jtag programmer cable from digilent +* [cmsisdap](https://os.mbed.com/docs/mbed-os/v6.11/debug-test/daplink.html): ARM CMSIS DAP protocol interface (hid only) * [DirtyJTAG](https://github.com/jeanthom/DirtyJTAG): JTAG probe firmware for STM32F1 (Best to use release (1.4 or newer) or limit the --freq to 600000 with older releases. New version https://github.com/jeanthom/DirtyJTAG/tree/dirtyjtag2 is also supported) * Intel USB Blaster I & II : jtag programmer cable from intel/altera @@ -106,7 +108,7 @@ __Supported cables:__ This application uses **libftdi1**, so this library must be installed (and, depending of the distribution, headers too) ```bash -apt-get install libftdi1-2 libftdi1-dev libudev-dev cmake +apt-get install libftdi1-2 libftdi1-dev libhidapi-libusb0 libhidapi-dev libudev-dev cmake ``` **libudev-dev** is optional, may be replaced by **eudev-dev** or just not installed. @@ -117,6 +119,13 @@ node). If you don't want this option, use: -DENABLE_UDEV=OFF ``` +By default, **cmsisdap** support is enabled (used for colorlight I5). +If you don't want this option, use: + +```bash +-DENABLE_CMSISDAP=OFF +``` + And if not already done, install **pkg-config**, **make** and **g++**. Alternatively you can manually specify the location of **libusb** and **libftdi1**: @@ -137,6 +146,7 @@ $ mkdir build $ cd build $ cmake ../ # add -DBUILD_STATIC=ON to build a static version # add -DENABLE_UDEV=OFF to disable udev support and -d /dev/xxx + # add -DENABLE_CMSISDAP=OFF to disable CMSIS DAP support $ cmake --build . or $ make -j$(nproc) diff --git a/src/cable.hpp b/src/cable.hpp index ff3aca4..7c5d1fc 100644 --- a/src/cable.hpp +++ b/src/cable.hpp @@ -14,7 +14,8 @@ enum communication_type { MODE_FTDI_BITBANG = 1, /*! used with ft232RL/ft231x */ MODE_FTDI_SERIAL = 2, /*! ft2232, ft232H */ MODE_DIRTYJTAG = 3, /*! JTAG probe firmware for STM32F1 */ - MODE_USBBLASTER = 4, /*! JTAG probe firmware for USBBLASTER */ + MODE_USBBLASTER = 4, /*! JTAG probe firmware for USBBLASTER */ + MODE_CMSISDAP = 5, /*! CMSIS-DAP JTAG probe */ }; typedef struct { @@ -28,6 +29,7 @@ static std::map cable_list = { {"anlogicCable", {MODE_ANLOGICCABLE, {}}}, {"bus_blaster", {MODE_FTDI_SERIAL, {0x0403, 0x6010, INTERFACE_A, 0x08, 0x1B, 0x08, 0x0B}}}, {"bus_blaster_b",{MODE_FTDI_SERIAL, {0x0403, 0x6010, INTERFACE_B, 0x08, 0x0B, 0x08, 0x0B}}}, + {"cmsisdap", {MODE_CMSISDAP, {0x0d28, 0x0204, 0, 0, 0, 0, 0 }}}, {"digilent", {MODE_FTDI_SERIAL, {0x0403, 0x6010, INTERFACE_A, 0xe8, 0xeb, 0x00, 0x60}}}, {"digilent_b", {MODE_FTDI_SERIAL, {0x0403, 0x6010, INTERFACE_B, 0xe8, 0xeb, 0x00, 0x60}}}, {"digilent_hs2", {MODE_FTDI_SERIAL, {0x0403, 0x6014, INTERFACE_A, 0xe8, 0xeb, 0x00, 0x60}}}, diff --git a/src/cmsisDAP.cpp b/src/cmsisDAP.cpp new file mode 100644 index 0000000..76d411f --- /dev/null +++ b/src/cmsisDAP.cpp @@ -0,0 +1,588 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + * Copyright (c) 2021 Gwenhael Goavec-Merou +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "display.hpp" + +#include "cmsisDAP.hpp" + +using namespace std; + +#define DAP_JTAG_SEQ_TDO_CAPTURE (1 << 7) +#define DAP_JTAG_SEQ_TMS_SHIFT(x) ((x & 0x01) << 6) +#define DAP_JTAG_SEQ_NB_TCK(x) (x & 0x3f) + +enum datalink_cmd { + DAP_INFO = 0x00, + DAP_HOSTSTATUS = 0x01, + DAP_CONNECT = 0x02, // Connect to device and select mode + DAP_DISCONNECT = 0x03, // Disconnect to device + DAP_SWJ_CLK = 0x11, // Select maximum frequency + DAP_SWJ_SEQUENCE = 0x12, // Generate TMS sequence + DAP_JTAG_SEQUENCE = 0x14 // Generate TMS, TDI and capture TDO Sequence +}; + +enum cmsisdap_connect_mode { + DAP_CONNECT_DFLT = 0x00, // Default mode: no configuration + DAP_CONNECT_SWD = 0x01, // Serial Wire Debug mode + DAP_CONNECT_JTAG = 0x02 // 4/5 pins JTAG mode +}; + +enum cmsisdap_info_id { + INFO_ID_VID = 0x01, // Get the Vendor ID (string). + INFO_ID_PID = 0x02, // Get the Product ID (string). + INFO_ID_SERNUM = 0x03, // Get the Serial Number (string). + INFO_ID_FWVERS = 0x04, // Get the CMSIS-DAP Firmware + // Version (string). + INFO_ID_TARGET_DEV_VENDOR = 0x05, // Get the Target Device Vendor (string). + INFO_ID_TARGET_DEV_NAME = 0x06, // Get the Target Device Name (string). + INFO_ID_HWCAP = 0xF0, // Get information about the + // Capabilities (BYTE) of the Debug Unit + INFO_ID_SWO_TEST_TIM_PARAM = 0xF1, // Get the Test Domain Timer parameter + INFO_ID_SWO_TRACE_BUF_SIZE = 0xFD, // Get the SWO Trace Buffer Size (WORD). + INFO_ID_MAX_PKT_CNT = 0xFE, // Get the maximum Packet Count (BYTE). + INFO_ID_MAX_PKT_SZ = 0xFF // Get the maximum Packet Size (SHORT). +}; + +static map cmsisdap_info_id_str = { + {INFO_ID_VID, "VID"}, + {INFO_ID_PID, "PID"}, + {INFO_ID_SERNUM, "serial number"}, + {INFO_ID_FWVERS, "firmware version"}, + + {INFO_ID_TARGET_DEV_VENDOR, "target device vendor"}, + {INFO_ID_TARGET_DEV_NAME, "target device name"}, + {INFO_ID_HWCAP, "hardware capabilities"}, + + {INFO_ID_SWO_TEST_TIM_PARAM, "test domain timer parameter"}, + {INFO_ID_SWO_TRACE_BUF_SIZE, "SWO trace buffer size"}, + {INFO_ID_MAX_PKT_CNT, "max packet cnt"}, + {INFO_ID_MAX_PKT_SZ, "max packet size"} +}; + +enum cmsisdap_info_type { + DAPLINK_INFO_STRING = 0x00, + DAPLINK_INFO_BYTE, + DAPLINK_INFO_SHORT, + DAPLINK_INFO_WORD +}; + +enum cmsisdap_status { + DAP_OK = 0x00, + DAP_ERROR = 0xff +}; + +CmsisDAP::CmsisDAP(int vid, int pid, bool verbose):_verbose(verbose), + _device_idx(0), _vid(vid), _pid(pid), + _serial_number(L""), _dev(NULL), _num_tms(0) +{ + std::vector dev_found; + _ll_buffer = (unsigned char *)malloc(sizeof(unsigned char) * 65); + if (!_ll_buffer) + std::runtime_error("internal buffer allocation failed"); + _buffer = _ll_buffer+2; + + /* only hid support */ + struct hid_device_info *devs, *cur_dev; + + if (hid_init() != 0) { + throw std::runtime_error("hidapi init failed"); + } + + /* search for HID compatible devices + * if vid/pid are 0 this function return all; + * if vid/pid are >0 only one (or 0) device returned + */ + devs = hid_enumerate(vid, pid); + + /* for all HID devices extracts only + * ones with a product_string containing the string "CMSIS-DAP" + */ + for (cur_dev = devs; NULL != cur_dev; cur_dev = cur_dev->next) { + if (NULL != cur_dev->product_string && + wcsstr(cur_dev->product_string, L"CMSIS-DAP")) { + dev_found.push_back(cur_dev); + } + } + + /* no devices: stop */ + if (dev_found.empty()) + throw std::runtime_error("No device found"); + /* more than one device: can't continue without more information */ + if (dev_found.size() > 1) + throw std::runtime_error( + "Error: more than one device. Please provides VID/PID"); + + printInfo("Found " + std::to_string(dev_found.size()) + " compatible device:"); + for (size_t i = 0; i < dev_found.size(); i++) { + char val[256]; + snprintf(val, sizeof(val), "\t0x%04x 0x%04x %ls", + dev_found[i]->vendor_id, + dev_found[i]->product_id, + dev_found[i]->product_string); + printInfo(val); + } + + /* store params about device to use */ + _vid = dev_found[_device_idx]->vendor_id; + _pid = dev_found[_device_idx]->product_id; + if (dev_found[_device_idx]->serial_number != NULL) + _serial_number = wstring(dev_found[_device_idx]->serial_number); + /* open the device */ + _dev = hid_open_path(dev_found[_device_idx]->path); + /* cleanup enumeration */ + hid_free_enumeration(devs); + + if (verbose) { + display_info(INFO_ID_VID , DAPLINK_INFO_STRING); + display_info(INFO_ID_PID , DAPLINK_INFO_STRING); + display_info(INFO_ID_SERNUM , DAPLINK_INFO_STRING); + display_info(INFO_ID_FWVERS , DAPLINK_INFO_STRING); + display_info(INFO_ID_TARGET_DEV_VENDOR , DAPLINK_INFO_STRING); + display_info(INFO_ID_TARGET_DEV_NAME , DAPLINK_INFO_STRING); + display_info(INFO_ID_HWCAP , DAPLINK_INFO_BYTE); + display_info(INFO_ID_SWO_TRACE_BUF_SIZE, DAPLINK_INFO_WORD); + display_info(INFO_ID_MAX_PKT_CNT , DAPLINK_INFO_BYTE); + display_info(INFO_ID_MAX_PKT_SZ , DAPLINK_INFO_SHORT); + } + + /* read device capabilities -> check if it's JTAG compatible + * 0 -> info + * 1 -> len (1: info0, 2: info0, info1) + * Available transfer protocols to target: + Info0 - Bit 0: 1 = SWD Serial Wire Debug communication is implemented + 0 = SWD Commands not implemented + Info0 - Bit 1: 1 = JTAG communication is implemented + 0 = JTAG Commands not implemented + Serial Wire Trace (SWO) support: + Info0 - Bit 2: 1 = SWO UART - UART Serial Wire Output is implemented + 0 = not implemented + Info0 - Bit 3: 1 = SWO Manchester - Manchester Serial Wire Output is implemented + 0 = not implemented + Command extensions for transfer protocol: + Info0 - Bit 4: 1 = Atomic Commands - Atomic Commands support is implemented + 0 = Atomic Commands not implemented + Time synchronisation via Test Domain Timer: + Info0 - Bit 5: 1 = Test Domain Timer - debug unit support for Test Domain Timer is implemented + 0 = not implemented + SWO Streaming Trace support: + Info0 - Bit 6: 1 = SWO Streaming Trace is implemented (0 = not implemented). + */ + memset(_buffer, 0, 65); + int res = read_info(INFO_ID_HWCAP, _buffer, 64); + if (res < 0) { + char t[256]; + snprintf(t, sizeof(t), "Error %d for command %d\n", res, INFO_ID_HWCAP); + throw std::runtime_error(t); + } + + if (verbose) + printf("Hardware cap %02x %02x %02x\n", _buffer[0], _buffer[1], _buffer[2]); + if (!(_buffer[2] & (1 << 1))) + throw std::runtime_error("JTAG is not supported by the probe"); + + /* send connect */ + if (dapConnect() != 1) + throw std::runtime_error("DAP connection in JTAG mode failed"); +} + +CmsisDAP::~CmsisDAP() +{ + /* TODO: disconnect device + * close device + */ + if (_ll_buffer) + free(_ll_buffer); +} + +/* send connect instruction (0x02) to switch + * in JTAG mode (0x02) + */ +int CmsisDAP::dapConnect() +{ + _ll_buffer[1] = DAP_CONNECT; + _ll_buffer[2] = DAP_CONNECT_JTAG; + uint8_t response[2]; + int ret = xfer(2, response, 2); + if (ret <= 0) + return ret; + if (response[0] != DAP_CONNECT || response[1] != DAP_CONNECT_JTAG) + return 0; + return 1; +} + +/* configure clk using instruction 0x11 followed + * by 32bits (LSB first) frequency in Hz + */ +int CmsisDAP::setClkFreq(uint32_t clkHZ) +{ + _clkHZ = clkHZ; + _buffer[3] = (uint8_t)(_clkHZ >> 24); + _buffer[2] = (uint8_t)(_clkHZ >> 16); + _buffer[1] = (uint8_t)(_clkHZ >> 8); + _buffer[0] = (uint8_t)(_clkHZ >> 0); + if (xfer(DAP_SWJ_CLK, 4, NULL, 0) <= 0) { + printError("Failed to configure clk frequency"); + return -1; + } else if (_verbose) { + printSuccess("clk frequency conf done"); + } + return 0; +} + +/* fill buffer with one or more tms state + * if tms count == 256 (max allowed by CMSIS-DAP) + * flush the buffer + * tms states are written only if max or if flush_buffer set + */ +int CmsisDAP::writeTMS(uint8_t *tms, int len, bool flush_buffer) +{ + /* nothing to send + * check if the buffer must be flushed + */ + if (len == 0) { + if (flush_buffer) + return flush(); + return 0; + } + + /* fill buffer with tms states */ + for (int pos = 0; pos < len; pos++) { + /* max tms states allowed by CMSIS-DAP -> flush */ + if (_num_tms == 256) { + if (flush() < 0) { + printError("Flush error"); + return -1; + } + } + + if (tms[pos >> 3] & (1 << (pos & 0x07))) + _buffer[(_num_tms >> 3)+1] |= (1 << (_num_tms & 0x07)); + else + _buffer[(_num_tms >> 3)+1] &= ~(1 << (_num_tms & 0x07)); + _num_tms++; + } + + /* flush is it's asked or if the buffer is full */ + if (flush_buffer || _num_tms == 256) + return flush(); + return len; +} + +/* 0x14 + number of sequence + seq1 details + tdi + seq2 details + tdi + ... + */ +int CmsisDAP::writeJtagSequence(uint8_t tms, uint8_t *tx, uint8_t *rx, + uint32_t len, bool end) +{ + int ret; + int real_len = len - (end ? 1 : 0); // full xfer size according to end + uint8_t *rx_ptr = rx, *tx_ptr = tx; // rd & wr ptr + int xfer_byte_len, xfer_bit_len; // size of one sequence + // in byte and bit + int byte_to_read = 0; // for rd operation number of read in one xfer + /* constant part of all sequences info byte */ + uint8_t seq_info_base = ((rx) ? DAP_JTAG_SEQ_TDO_CAPTURE : 0) | + DAP_JTAG_SEQ_TMS_SHIFT(tms); + int seq_num = 0; // count number of sequence in buffer + int pos = 1; // 0: num of sequence, 1: seq1 detail + int xfer_rest = real_len; // main loop + + flush(); // force TMS flush to free _buffer + + while (xfer_rest > 0) { + if (xfer_rest >= 64) { // fully fill one sequence + xfer_byte_len = 8; + xfer_bit_len = 64; + } else { // fill one sequence with rest + xfer_byte_len = (xfer_rest + 7) / 8; + xfer_bit_len = xfer_rest; + } + + /* buffer is 65bits with + * [0] : hid + * [1] : cmsisdap operation + * [2] : number of sequence + * [64:3]: sequence with + * [n] : sequence infos + * [n+m+1:n+1]: data + * So only 62 bits are available to send sequences + * and 64bits (full sequence) mean 8bits + * => one sequence == 9Bytes and 9*7 == 63 + * then we have 6 * 8 fully filled sequence + one up to 56bits + */ + if (xfer_byte_len + 1 + pos > 63) { + xfer_byte_len = 63 - pos - 1; // number of free bytes + xfer_bit_len = xfer_byte_len * 8; + } + + /* update sequence info with number of bit */ + _buffer[pos++] = seq_info_base | + DAP_JTAG_SEQ_NB_TCK((xfer_bit_len == 64?0:xfer_bit_len)); + if (tx) { // use tx only if not NULL + memcpy(&_buffer[pos], (unsigned char *)tx_ptr, xfer_byte_len); + tx_ptr += xfer_byte_len; + } + xfer_rest -= xfer_bit_len; // update remaining number of bit + seq_num++; // update sequence counter + pos += xfer_byte_len; // update buffer position + byte_to_read += xfer_byte_len; // update read lenght + + /* when it's the last sequence or + * buffer is fully filled + * => flush + * if it's the last sequence and end is true, don't do anything + * here -> see bellow + */ + if ((!end && xfer_rest == 0) || seq_num == 7) { + _buffer[0] = seq_num; // set number of sequences + ret = xfer(DAP_JTAG_SEQUENCE, pos, + (rx) ? rx_ptr: NULL, byte_to_read); + if (ret <= 0) { + printError("writeTDI: failed to send sequence"); + return ret; + } + if (rx) // if read: move pointer to the next position + rx_ptr += byte_to_read; + + /* reset all variables */ + pos = 1; + seq_num = 0; // no sequence + byte_to_read = 0; + } + } + + /* add a dedicated sequence to the last bit + * with !tms in info + * used with writeTDI to change TMS state at the same time + * as last bit to send + */ + if (end) { + byte_to_read++; // residual (or 0) from previous iter + 1 Byte + uint8_t val[byte_to_read]; + _buffer[0] = seq_num + 1; + _buffer[pos++] = ((rx) ? DAP_JTAG_SEQ_TDO_CAPTURE : 0) | + DAP_JTAG_SEQ_TMS_SHIFT(0x01&(!tms)) | + DAP_JTAG_SEQ_NB_TCK(1); + _buffer[pos++] = (tx[(real_len) >> 3] & (1 << (real_len & 0x07))) ? 1 : 0; + ret = xfer(DAP_JTAG_SEQUENCE, pos, (rx) ? val: NULL, byte_to_read); + if (ret <= 0) { + printError("writeTDI: failed to send last sequence"); + return ret; + } + if (rx) { + memcpy(rx_ptr, val, byte_to_read-1); + if (val[byte_to_read-1] & 0x01) + rx[real_len >> 3] |= 1 << ((real_len) & 0x07); + else + rx[real_len >> 3] &= ~(1 << ((real_len) & 0x07)); + } + } + + return len; +} + +/* send TDI by filling jtag sequence + * tx buffer is considered to be correctly aligned (LSB first) + */ +int CmsisDAP::writeTDI(uint8_t *tx, uint8_t *rx, uint32_t len, bool end) +{ + return writeJtagSequence(0, tx, rx, len, end); +} + +/* unlike TMS the is no dedicated instruction to toggle clk + * so fill a buffer with tdi state and call same method as writeTDI + */ +int CmsisDAP::toggleClk(uint8_t tms, uint8_t tdi, uint32_t clk_len) +{ + const int byte_len = (clk_len + 7) / 8; + uint8_t tx[byte_len]; + memset(tx, (tdi) ? 0xff : 0x00, byte_len); + /* use false as last param to maintain tms in the current state */ + return writeJtagSequence(tms, tx, NULL, clk_len, false); +} + +/* flush buffer filled with TMS states + */ +int CmsisDAP::flush() +{ + int ret; + if (_num_tms == 0) + return 0; + _buffer[0] = (uint8_t)(_num_tms & 0xff); + // +1 (buff size) + ret = xfer(DAP_SWJ_SEQUENCE, ((_num_tms + 7) / 8) + 1, NULL, 0); + _num_tms = 0; + return ret; +} + +/* fill low level buffer with + * 0: 0] -> hid + * 1: instruction + * 2->n: message + * check if response contains instructions + status (with status == 0x00 + * if read copy 2-n + */ +int CmsisDAP::xfer(uint8_t instruction, int tx_len, + uint8_t *rx_buff, int rx_len) +{ + _ll_buffer[0] = 0; + _ll_buffer[1] = instruction; + + int ret = hid_write(_dev, _ll_buffer, 65); + if (ret == -1) { + printf("Error\n"); + return ret; + } + + ret = hid_read_timeout(_dev, _ll_buffer, 65, 1000); + if (ret <= 0) { + if (ret == 0) + printError("Error timeout\n"); + else if (ret == -1) + printError("Error comm\n"); + return ret; + } + if (_ll_buffer[0] != instruction && _ll_buffer[1] != DAP_OK) { + printf("Error: command error"); + return -1; + } + + if (rx_buff) { + memcpy(rx_buff, _buffer, rx_len); + } + + return ret; +} + +/* same as previous method but + * 1/ instruction is already in tx_buff + * 2/ no check is done to device answer + */ +int CmsisDAP::xfer(int tx_len, uint8_t *rx_buff, int rx_len) +{ + _ll_buffer[0] = 0; + + int ret = hid_write(_dev, _ll_buffer, 65); + if (ret == -1) { + printf("Error\n"); + return ret; + } + + ret = hid_read_timeout(_dev, _ll_buffer, 65, 1000); + if (ret <= 0) { + if (ret == 0) + printf("Error timeout\n"); + else if (ret == -1) + printf("Error comm\n"); + return ret; + } + if (rx_len) + memcpy(rx_buff, _ll_buffer, rx_len); + + + return ret; +} + +int CmsisDAP::read_info(uint8_t info, uint8_t *rd_info, int max_len) +{ + _ll_buffer[1] = DAP_INFO; + _ll_buffer[2] = info; + int ret = xfer(2, rd_info, max_len); + if (ret <= 0) + return ret; + else + return static_cast(rd_info[1]); +} + +void CmsisDAP::display_info(uint8_t info, uint8_t type) +{ + uint8_t buffer[65]; + memset(buffer, 0, 65); + int ret = read_info(info, buffer, 64); + if (ret < 0) { + printf("received error %d for command %d\n", ret, info); + return; + } + + if (ret == 0) { + char val[256]; + if (info == INFO_ID_VID) { + snprintf(val, sizeof(val), "\t%s: %04x", + cmsisdap_info_id_str[info].c_str(), _vid); + } else if (info == INFO_ID_PID) { + snprintf(val, sizeof(val), "\t%s: %04x", + cmsisdap_info_id_str[info].c_str(), _pid); + } else if (info == INFO_ID_SERNUM) { + if (!_serial_number.empty()) { + snprintf(val, sizeof(val), "\t%s: %ls", + cmsisdap_info_id_str[info].c_str(), + _serial_number.c_str()); + } else { + printError("\t" + cmsisdap_info_id_str[info] + " : NA"); + return; + } + } else if (info == INFO_ID_TARGET_DEV_NAME || \ + info == INFO_ID_TARGET_DEV_VENDOR) { + /* nothing */ + return; + } else { + printError("\t" + cmsisdap_info_id_str[info] + " : NA"); + return; + } + printInfo(val); + + return; + } + + bool fail = true; + + if (type == DAPLINK_INFO_BYTE && ret != 1) { + printf("Error: Waiting for 1Byte received %d\n", ret); + } else if (type == DAPLINK_INFO_SHORT && ret != 2) { + printf("Error: Waiting for 2Byte received %d\n", ret); + } else if (type == DAPLINK_INFO_WORD && ret != 4) { + printf("Error: Waiting for 2Byte received %d\n", ret); + } else { + fail = false; + } + + if (fail == true) { + for (int i = 0; i < 64; i++) { + printf("%02x ", buffer[i]); + } + printf("\n"); + return; + } + + printInfo("\t" + cmsisdap_info_id_str[info] + " : ", false); + + if (type == DAPLINK_INFO_BYTE) { + printf("%02x\n", buffer[2]); + } else if (type == DAPLINK_INFO_SHORT) { + uint16_t val = (buffer[3] << 8) | buffer[2]; + printf("%d\n", val); + } else if (type == DAPLINK_INFO_WORD) { + uint32_t val = (buffer[5] << 24) | (buffer[4] << 16) | + (buffer[3] << 8) | buffer[2]; + printf("%u\n", val); + } else { + char val[ret]; + memcpy(val, &buffer[2], ret); + printf("%s\n", val); + } +} diff --git a/src/cmsisDAP.hpp b/src/cmsisDAP.hpp new file mode 100644 index 0000000..84f0357 --- /dev/null +++ b/src/cmsisDAP.hpp @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + * Copyright (c) 2021 Gwenhael Goavec-Merou +*/ + +#ifndef SRC_CMSISDAP_HPP_ +#define SRC_CMSISDAP_HPP_ + +#include +#include + +#include +#include + +#include "jtagInterface.hpp" + +class CmsisDAP: public JtagInterface { + public: + /*! + * \brief contructor: open device with vid/pid if != 0 + * else search for a compatible device + * \param[in] vid: vendor id + * \param[in] pid: product id + * \param[in] verbose: verbose level false normal, true verbose + */ + CmsisDAP(const int vid, const int pid, bool verbose); + + ~CmsisDAP(); + + /*! + * \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, int 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 TMS buffer + * \return <=0 if something fail, > 0 otherwise + */ + int flush() override; + + /* not used */ + int get_buffer_size() override { return 0;} + /* not used */ + bool isFull() override {return false;} + + private: + /*! + * \brief connect device in JTAG mode + * \return 1 if success <= 0 otherwhise + */ + int dapConnect(); + int read_info(uint8_t info, uint8_t *rd_info, int max_len); + int xfer(int tx_len, uint8_t *rx_buff, int rx_len); + int xfer(uint8_t instruction, int tx_len, + uint8_t *rx_buff, int rx_len); + + void display_info(uint8_t info, uint8_t type); + int writeJtagSequence(uint8_t tms, uint8_t *tx, uint8_t *rx, + uint32_t len, bool end); + + bool _verbose; /**< display more message */ + int16_t _device_idx; /**< device index */ + uint16_t _vid; /**< device Vendor ID */ + uint16_t _pid; /**< device Product ID */ + std::wstring _serial_number; /**< device serial number */ + + hid_device *_dev; /**< hid device used to communicate */ + + unsigned char *_ll_buffer; /**< message buffer */ + unsigned char *_buffer; /**< subset of _ll_buffer */ + int _num_tms; /**< current tms length */ +}; + +#endif // SRC_CMSISDAP_HPP_ diff --git a/src/jtag.cpp b/src/jtag.cpp index c7aa13c..2b8501e 100644 --- a/src/jtag.cpp +++ b/src/jtag.cpp @@ -30,6 +30,9 @@ #include "ftdipp_mpsse.hpp" #include "ftdiJtagBitbang.hpp" #include "ftdiJtagMPSSE.hpp" +#ifdef ENABLE_CMSISDAP +#include "cmsisDAP.hpp" +#endif #include "dirtyJtag.hpp" #include "part.hpp" #include "usbBlaster.hpp" @@ -107,6 +110,11 @@ void Jtag::init_internal(cable_t &cable, const string &dev, const string &serial _jtag = new UsbBlaster(cable.config.vid, cable.config.pid, firmware_path, _verbose); break; +#ifdef ENABLE_CMSISDAP + case MODE_CMSISDAP: + _jtag = new CmsisDAP(cable.config.vid, cable.config.pid, _verbose); + break; +#endif default: std::cerr << "Jtag: unknown cable type" << std::endl; throw std::exception();