From 827767b99fc0aef52bb59a07d7663b8ba670da53 Mon Sep 17 00:00:00 2001 From: Gwenhael Goavec-Merou Date: Sun, 20 Jun 2021 10:25:49 +0200 Subject: [PATCH] add proof of concept / draft for DFU protocol. Add orangeCrab in DFU mode --- CMakeLists.txt | 2 + README.md | 3 + src/board.hpp | 6 +- src/cable.hpp | 2 + src/dfu.cpp | 727 +++++++++++++++++++++++++++++++++++++++++++++++++ src/dfu.hpp | 227 +++++++++++++++ src/main.cpp | 35 ++- 7 files changed, 1000 insertions(+), 2 deletions(-) create mode 100644 src/dfu.cpp create mode 100644 src/dfu.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e55d7da..e4957a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,6 +46,7 @@ set(OPENFPGALOADER_SOURCE src/anlogic.cpp src/anlogicBitParser.cpp src/anlogicCable.cpp + src/dfu.cpp src/dfuFileParser.cpp src/dirtyJtag.cpp src/efinix.cpp @@ -85,6 +86,7 @@ set(OPENFPGALOADER_HEADERS src/anlogicBitParser.hpp src/anlogicCable.hpp src/cxxopts.hpp + src/dfu.hpp src/dfuFileParser.hpp src/dirtyJtag.hpp src/efinix.hpp diff --git a/README.md b/README.md index 57727bc..7654bf9 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ __Current supported kits:__ * [Lattice ECP5 5G Evaluation Board (LFE5UM5G-85F-EVN)](https://www.latticesemi.com/en/Products/DevelopmentBoardsAndKits/ECP5EvaluationBoard) (memory and spi flash) * [Olimex iCE40HX1K-EVB](https://www.olimex.com/Products/FPGA/iCE40/iCE40HX1K-EVB/open-source-hardware) * [Olimex iCE40HX8K-EVB](https://www.olimex.com/Products/FPGA/iCE40/iCE40HX8K-EVB/open-source-hardware) +* [Orange Crab](https://github.com/gregdavill/OrangeCrab) * [QMTech CycloneV Core Board](https://fr.aliexpress.com/i/1000006622149.html) (memory) * [Trenz Gowin LittleBee (TEC0117)](https://shop.trenz-electronic.de/en/TEC0117-01-FPGA-Module-with-GOWIN-LittleBee-and-8-MByte-internal-SDRAM) (memory and flash) * [Saanlima Pipistrello LX45](http://pipistrello.saanlima.com/index.php?title=Welcome_to_Pipistrello) (memory) @@ -72,6 +73,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) +* [DFU (Device Firmware Upgrade)](http://www.usb.org/developers/docs/devclass_docs/DFU_1.1.pdf): USB device compatible with DFU protocol * [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 @@ -187,6 +189,7 @@ openFPGALoader -- a program to flash FPGA A-D) -d, --device arg device to use (/dev/ttyUSBx) --detect detect FPGA + --dfu DFU mode --file-type arg provides file type instead of let's deduced by using extension --fpga-part arg fpga model flavor + package diff --git a/src/board.hpp b/src/board.hpp index 5dafa33..3256ed0 100644 --- a/src/board.hpp +++ b/src/board.hpp @@ -58,7 +58,8 @@ typedef struct { enum { COMM_JTAG = (1 << 0), - COMM_SPI = (1 << 1) + COMM_SPI = (1 << 1), + COMM_DFU = (1 << 2), }; /*! @@ -83,6 +84,8 @@ typedef struct { #define SPI_BOARD(_name, _manufacturer, _cable, _rst, _done, _cs, _sck, _si, _so, _holdn, _wpn) \ {_name, {_manufacturer, _cable, "", _rst, _done, COMM_SPI, {}, \ {_cs, _sck, _so, _si, _holdn, _wpn}}} +#define DFU_BOARD(_name, _fpga_part, _cable) \ + {_name, {"", _cable, _fpga_part, 0, 0, COMM_DFU, {}, {}}} static std::map board_list = { JTAG_BOARD("acornCle215", "xc7a200tsbg484", "", 0, 0), @@ -116,6 +119,7 @@ static std::map board_list = { JTAG_BOARD("spartanEdgeAccelBoard", "", "",0, 0), JTAG_BOARD("pipistrello", "", "ft2232", 0, 0), JTAG_BOARD("minispartan6", "", "ft2232", 0, 0), + DFU_BOARD("orangeCrab", "", "dfu" ), JTAG_BOARD("qmtechCycloneV", "", "", 0, 0), JTAG_BOARD("runber", "", "ft232", 0, 0), JTAG_BOARD("tangnano", "", "ft2232", 0, 0), diff --git a/src/cable.hpp b/src/cable.hpp index 7c5d1fc..ce666c8 100644 --- a/src/cable.hpp +++ b/src/cable.hpp @@ -16,6 +16,7 @@ enum communication_type { MODE_DIRTYJTAG = 3, /*! JTAG probe firmware for STM32F1 */ MODE_USBBLASTER = 4, /*! JTAG probe firmware for USBBLASTER */ MODE_CMSISDAP = 5, /*! CMSIS-DAP JTAG probe */ + MODE_DFU = 6, /*! DFU based probe */ }; typedef struct { @@ -30,6 +31,7 @@ static std::map cable_list = { {"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 }}}, + {"dfu", {MODE_DFU, {}}}, {"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/dfu.cpp b/src/dfu.cpp new file mode 100644 index 0000000..00999e3 --- /dev/null +++ b/src/dfu.cpp @@ -0,0 +1,727 @@ +// 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 "display.hpp" +#include "progressBar.hpp" + +#include "dfu.hpp" + +using namespace std; + +/* USB request write */ +static const uint8_t DFU_REQUEST_OUT = LIBUSB_ENDPOINT_OUT | + LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE; +/* USB request read */ +static const uint8_t DFU_REQUEST_IN = LIBUSB_ENDPOINT_IN | + LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE; + +/* DFU command */ +enum dfu_cmd { + DFU_DETACH = 0, + DFU_DNLOAD = 1, + DFU_UPLOAD = 2, + DFU_GETSTATUS = 3, + DFU_CLRSTATUS = 4, + DFU_GETSTATE = 5, + DFU_ABORT = 6 +}; + +/* Question: - + * - add vid/pid and override DFU file ? + * - index as jtag chain (fix issue when more than one device connected) + */ + +DFU::DFU(const string &filename, int verbose_lvl):_verbose(verbose_lvl > 0), + _quiet(verbose_lvl < 0), dev_idx(0), _vid(0), _pid(0), + usb_ctx(NULL), dev_handle(NULL), curr_intf(0), transaction(0), + _bit(NULL) +{ + struct dfu_status status; + + if (!filename.empty()) { + printInfo("Open file " + filename + " ", false); + try { + _bit = new DFUFileParser(filename, _verbose > 0); + printSuccess("DONE"); + } catch (std::exception &e) { + printError("FAIL"); + throw runtime_error("Error: Fail to open file"); + } + + printInfo("Parse file ", false); + try { + _bit->parse(); + printSuccess("DONE"); + } catch (std::exception &e) { + printError("FAIL"); + delete _bit; + throw runtime_error("Error: Fail to parse file"); + } + + if (_verbose > 0) + _bit->displayHeader(); + + /* get VID and PID from dfu file */ + try { + _vid = std::stoi(_bit->getHeaderVal("idVendor"), 0, 16); + _pid = std::stoi(_bit->getHeaderVal("idProduct"), 0, 16); + printf("0x%04x 0x%04x\n", _vid, _pid); + } catch (std::exception &e) { + if (_verbose) + printWarn(e.what()); + } + } + + if (libusb_init(&usb_ctx) < 0) { + delete _bit; + throw std::runtime_error("libusb init failed"); + } + + /* no vid or pid provided by DFU file */ + if (_vid == 0 || _pid == 0) { // search all DFU compatible devices + if (searchDFUDevices() != EXIT_SUCCESS) { + delete _bit; + throw std::runtime_error("Devices enumeration failed"); + } + } else { + /* search device using vid/pid */ + libusb_device_handle *handle = libusb_open_device_with_vid_pid(usb_ctx, + _vid, _pid); + if (!handle) { + delete _bit; + throw std::runtime_error("Error: unable to connect to device"); + } + + /* retrieve usb device structure */ + libusb_device *dev = libusb_get_device(handle); + if (!dev) { + libusb_close(handle); + delete _bit; + throw std::runtime_error("Error: unable to retrieve usb device"); + } + + /* and device descriptor */ + struct libusb_device_descriptor desc; + int r = libusb_get_device_descriptor(dev, &desc); + if (r != 0) { + libusb_close(handle); + delete _bit; + throw std::runtime_error("Error: fail to retrieve usb descriptor"); + } + + /* search if one descriptor is DFU compatible */ + searchIfDFU(dev, &desc); + + libusb_close(handle); // no more needed -> reopen after + } + + /* check if DFU compatible devices are present */ + if (dfu_dev.size() != 0) { + /* more than one: only possible if file is not DFU */ + if (dfu_dev.size() > 1) + throw std::runtime_error("Only one device supported"); + } else { + throw std::runtime_error("No DFU compatible device found"); + } + + if (_verbose) + displayDFU(); + + /* open the first */ + if (open_DFU(0) == EXIT_FAILURE) { + delete _bit; + throw std::runtime_error("Fail to claim device"); + } + + char state = get_state(); + if (_verbose > 0) { + printInfo("Default DFU status " + dfu_dev_state_val[state]); + get_status(&status); + } +} + +DFU::~DFU() +{ + close_DFU(); + libusb_exit(usb_ctx); + delete _bit; +} + +/* open the device using VID and PID + */ +int DFU::open_DFU(int index) +{ + struct dfu_dev curr_dfu; + + dev_idx = index; + curr_dfu = dfu_dev[dev_idx]; + curr_intf = curr_dfu.interface; + + dev_handle = libusb_open_device_with_vid_pid(usb_ctx, + curr_dfu.vid, curr_dfu.pid); + if (!dev_handle) { + printError("Error: fail to open device"); + return EXIT_FAILURE; + } + if (libusb_claim_interface(dev_handle, curr_intf) != 0) { + libusb_close(dev_handle); + printError("Error: fail to claim interface"); + return EXIT_FAILURE; + } + if (libusb_set_interface_alt_setting(dev_handle, curr_intf, 0) != 0) { + libusb_release_interface(dev_handle, curr_intf); + libusb_close(dev_handle); + printError("Error: fail to set interface"); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +/* if the is claimed close it */ +int DFU::close_DFU() { + if (dev_handle) { + int ret; + ret = libusb_release_interface(dev_handle, dfu_dev[dev_idx].interface); + if (ret != 0) { + /* device is already disconnected ... */ + if (ret == LIBUSB_ERROR_NO_DEVICE) { + return EXIT_SUCCESS; + } else { + printError("Error: Fail to release interface"); + return EXIT_FAILURE; + } + } + libusb_close(dev_handle); + dev_handle = NULL; + } + return EXIT_SUCCESS; +} + +/* Tree steps are required to discover all + * DFU capable devices + * 1. loop over devices + */ +int DFU::searchDFUDevices() +{ + int i = 0; + libusb_device **dev_list; + libusb_device *usb_dev; + + /* clear dfu list */ + dfu_dev.clear(); + + /* iteration */ + ssize_t list_size = libusb_get_device_list(usb_ctx, &dev_list); + if (_verbose) + printInfo("found " + to_string(list_size) + " USB device"); + + 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 (_verbose > 0) { + printf("%04x:%04x (bus %d, device %2d)", + desc.idVendor, desc.idProduct, + libusb_get_bus_number(usb_dev), + libusb_get_device_address(usb_dev)); + } + + if (searchIfDFU(usb_dev, &desc) != 0) { + return EXIT_FAILURE; + } + } + + libusb_free_device_list(dev_list, 1); + + return EXIT_SUCCESS; +} + +/* 2. loop over configuration + */ +int DFU::searchIfDFU(struct libusb_device *dev, + struct libusb_device_descriptor *desc) +{ + /* configuration descriptor iteration */ + for (int i = 0; i < desc->bNumConfigurations; i++) { + struct libusb_config_descriptor *cfg; + int ret = libusb_get_config_descriptor(dev, i, &cfg); + if (ret != 0) { + printError("Fail to retrieve config_descriptor " + to_string(i)); + return 1; + } + /* configuration interface iteration */ + for (int if_idx=0; if_idx < cfg->bNumInterfaces; if_idx++) { + const struct libusb_interface *uif = &cfg->interface[if_idx]; + + /* altsettings interation */ + 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 (intfClass == 0xfe && intfSubClass == 0x01) { + struct dfu_dev my_dev; + if (_verbose) + printInfo("DFU found"); + + /* dfu functional descriptor */ + if (parseDFUDescriptor(intf, reinterpret_cast(&my_dev.dfu_desc), + sizeof(my_dev.dfu_desc)) != 0) + continue; // not compatible + my_dev.vid = desc->idVendor; + my_dev.pid = desc->idProduct; + my_dev.interface = if_idx; + my_dev.bus = libusb_get_bus_number(dev); + my_dev.device = libusb_get_device_address(dev); + my_dev.bMaxPacketSize0 = desc->bMaxPacketSize0; + int r = libusb_get_port_numbers(dev, my_dev.path, sizeof(my_dev.path)); + my_dev.path[r] = '\0'; + dfu_dev.push_back(my_dev); + } + } + } + + libusb_free_config_descriptor(cfg); + } + + return 0; +} + +/* 3. check if altsettings contains a valid + * dfu descriptor. + * Since libusb has no support for those structure + * fill a custom structure + */ +int DFU::parseDFUDescriptor(const struct libusb_interface_descriptor *intf, + uint8_t *dfu_desc, int dfu_desc_size) +{ + const uint8_t *extra = intf->extra; + int extra_len = intf->extra_length; + + if (extra_len < 9) + return -1; + + /* map memory to structure */ + for (int j = 0; j + 1 < extra_len; j++) { + if (extra[j+1] == 0x21) { + memcpy(dfu_desc, (const void *)&extra[j], dfu_desc_size); + break; + } + } + + return 0; +} + +/* abstraction to send/receive to control */ +int DFU::send(bool out, uint8_t brequest, uint16_t wvalue, + unsigned char *data, uint16_t length) +{ + uint8_t type = out ? DFU_REQUEST_OUT : DFU_REQUEST_IN; + int ret = libusb_control_transfer(dev_handle, type, + brequest, wvalue, curr_intf, + data, length, 5000); + if (ret < 0) { + if (checkDevicePresent()) { + /* close device ? */ + return 0; + } + } + return ret; +} + +/* subset of state transitions + */ + +int DFU::set_state(char newState) +{ + int ret = 0; + struct dfu_status status; + char curr_state = get_state(); + while (curr_state != newState) { + switch (curr_state) { + case STATE_appIDLE: + if (dfu_detach() == EXIT_FAILURE) + return -1; + if (get_status(&status) <= 0) + return -1; + if (status.bState != STATE_appDETACH || + status.bStatus != STATUS_OK) { + cerr << dfu_dev_status_val[status.bStatus] << endl; + return -1; + } + curr_state = status.bState; + break; + case STATE_appDETACH: + if (newState == STATE_appIDLE) { + // ? reset + timeout ? + } else { + // reset + // reenum + } + break; + case STATE_dfuIDLE: + /* better to test upload/download + * an handle others states */ + if (newState == STATE_appIDLE) { + // reset + // reenum + } else { // download or upload + // are handled by download() and upload() + return -2; + } + break; + case STATE_dfuDNLOAD_IDLE: + if (newState == STATE_dfuMANIFEST_SYNC) { + /* send the zero sized download request + * dfuDNLOAD_IDLE -> dfuMANITEST-SYNC */ + if (_verbose) + printInfo("send zero sized download request"); + ret = send(true, DFU_DNLOAD, transaction, NULL, 0); + } else { // dfuIDLE + ret = send(true, DFU_ABORT, 0, NULL, 0); + } + + if (ret < 0) { + printError("Fails to send packet\n"); + return ret; + } + if ((ret = get_state()) < 0) + return ret; + /* not always true: + * the newState may be the next one or another */ + if (ret != newState) { + printError(dfu_dev_state_val[ret]); + return -1; + } + curr_state = ret; + break; + case STATE_dfuERROR: + if (newState == STATE_appIDLE) { + ret = libusb_reset_device(dev_handle); + } else { // dfuIDLE + ret = send(true, DFU_CLRSTATUS, 0, NULL, 0); + } + /* if command fails + * try to determine if device is lost + * or if it's another issue */ + if (ret < 0) { + if (checkDevicePresent() == 1) { + printError("Fails to send packet\n"); + } else { + printInfo("device disconnected\n"); + } + return ret; + } + /* now state must be appIDLE or dfuIDLE + * use GETSTATUS to check */ + if ((ret = get_status(&status)) <= 0) + return ret; + if (status.bState != newState || + status.bStatus != STATUS_OK) { + cerr << dfu_dev_status_val[status.bStatus] << endl; + return -1; + } + curr_state = status.bState; + break; + } + } + + return ret; +} + +/* detach device -> move from appIDLE to dfuIDLE + */ +int DFU::dfu_detach() +{ + int res = send(true, DFU_DETACH, 0, NULL, 0); + return (res < 0) ? EXIT_FAILURE : EXIT_SUCCESS; +} + +int DFU::get_status(struct dfu_status *status) +{ + uint8_t buffer[6]; + int res; + + res = send(false, DFU_GETSTATUS, 0, buffer, 6); + + if (res == 6) { + status->bStatus = buffer[0]; + status->bwPollTimeout = (((uint32_t)buffer[3] & 0xff) << 16) || + (((uint32_t)buffer[2] & 0xff) << 8) || + (((uint32_t)buffer[1] & 0xff) << 0); + status->bState = buffer[4]; + status->iString = buffer[5]; + } + return res; +} + +/* read current device state -> unlike status + * this request didn't change state + */ +char DFU::get_state() +{ + char c; + + int res = send(false, DFU_GETSTATE, 0, (unsigned char *)&c, 1); + + if (res != 1) + return res; + return c; +} + +/* read status until device state match + * wait for bwPollTimeout ms between each requests + */ +int DFU::poll_state(uint8_t state) { + int ret = 0; + struct dfu_status status; + + do { + ret = get_status(&status); + if (ret <= 0) { + printError("Error: poll state " + string(libusb_error_name(ret))); + break; + } + /* millisecond */ + usleep(1000 * status.bwPollTimeout); + } while (status.bState != state && + status.bState != STATE_dfuERROR); + + return (ret > 0) ? status.bState : ret; +} + +/* display details about device informations and capabilities + */ +void DFU::displayDFU() +{ + /* display dfu device */ + printf("Found DFU:\n"); + for (unsigned int i = 0; i < dfu_dev.size(); i++) { + printf("%04x:%04x (bus %d, device %2d)", + dfu_dev[i].vid, dfu_dev[i].pid, + dfu_dev[i].bus, dfu_dev[i].device); + printf(" path: %d", dfu_dev[i].path[0]); + for (size_t j = 1; j < strlen(((const char *)dfu_dev[i].path)); j++) + printf(".%d", dfu_dev[i].path[j]); + printf("\n"); + printf("\tDFU details\n"); + printf("\t\tbLength %02x\n", dfu_dev[i].dfu_desc.bLength); + printf("\t\tbDescriptorType %02x\n", + dfu_dev[i].dfu_desc.bDescriptorType); + printf("\t\tbmAttributes %02x\n", dfu_dev[i].dfu_desc.bmAttributes); + printf("\t\twDetachTimeOut %04x\n", + dfu_dev[i].dfu_desc.wDetachTimeOut); + printf("\t\twTransferSize %04d\n", + libusb_le16_to_cpu(dfu_dev[i].dfu_desc.wTransferSize)); + printf("\t\tbcdDFUVersion %04x\n", + libusb_le16_to_cpu(dfu_dev[i].dfu_desc.bcdDFUVersion)); + uint8_t bmAttributes = dfu_dev[i].dfu_desc.bmAttributes; + printf("\tDFU attributes %02x\n", bmAttributes); + printf("\t\tDFU_DETACH : "); + if (bmAttributes & (1 << 3)) + printf("ON\n"); + else + printf("OFF\n"); + printf("\t\tBitManifestionTolerant: "); + if (bmAttributes & (1 << 2)) + printf("ON\n"); + else + printf("OFF\n"); + printf("\t\tUPLOAD : "); + if (bmAttributes & (1 << 1)) + printf("ON\n"); + else + printf("OFF\n"); + printf("\t\tDOWNLOAD : "); + if (bmAttributes & (1 << 0)) + printf("ON\n"); + else + printf("OFF\n"); + } +} + +/*! + * \brief download filename content + * \param[in] filename: name of the file to download + * \return -1 when issue with file + * -2 when file parse fail + */ +int DFU::download() +{ + int ret, ret_val = EXIT_SUCCESS; + uint8_t *buffer, *ptr; + int size, xfer_len; + int bMaxPacketSize0 = dfu_dev[dev_idx].bMaxPacketSize0; + + struct dfu_status status; + struct dfu_dev curr_dev = dfu_dev[dev_idx]; + + /* check if device allows download */ + if (!(curr_dev.dfu_desc.bmAttributes & (1 << 0))) { + printError("Error: download is not supported by the device\n"); + return -1; + } + + /* download must start in dfu IDLE state */ + if (get_state() != STATE_dfuIDLE) + set_state(STATE_dfuIDLE); + + xfer_len = libusb_le16_to_cpu(curr_dev.dfu_desc.wTransferSize); + if (xfer_len < bMaxPacketSize0) + xfer_len = bMaxPacketSize0; + + size = _bit->getLength() / 8; + buffer = _bit->getData(); + + if (size == 0) { + printError("Error: empty configuration file\n"); + return -3; + } + + ptr = buffer; + + int max_iter = size / xfer_len; + if (max_iter * xfer_len != size) + max_iter++; + + ProgressBar progress("Loading", max_iter, 50, _quiet); + + /* send data configuration by up to xfer_len bytes */ + for (transaction = 0; transaction < max_iter; transaction++, ptr+=xfer_len) { + int residual = size - (xfer_len * transaction); + if (residual < xfer_len) + xfer_len = residual; + + ret = send(true, DFU_DNLOAD, transaction, + (xfer_len) ? ptr : NULL, xfer_len); + if (ret != xfer_len) { // can't be wrong here + printf("Fails to send packet %s\n", libusb_error_name(ret)); + ret_val = -4; + break; + } + + /* wait until dfu state is again STATE_dfuDNLOAD_IDLE + * using status pollTimeout value + */ + ret_val = poll_state(STATE_dfuDNLOAD_IDLE); + + if (ret_val != STATE_dfuDNLOAD_IDLE) { + printf("download: failed %d %d\n", ret_val, STATE_dfuDNLOAD_IDLE); + break; + } + progress.display(transaction); + } + + if (ret_val != STATE_dfuDNLOAD_IDLE) { + progress.fail(); + ret_val = -5; + return ret_val; + } + + progress.done(); + + /* send the zero sized download request + * dfuDNLOAD_IDLE -> dfuMANITEST-SYNC + */ + ret = set_state(STATE_dfuMANIFEST_SYNC); + if (ret < 0) { + printError("Error: fail to change state " + to_string(ret)); + return -6; + } + + /* Now FSM must be in dfuMANITEST-SYNC */ + bool must_continue = true; + do { + ret = get_status(&status); + if (ret != 6) { + /* we consider device disconnected + * is ret == 0 + */ + if (ret < 0 && ret != LIBUSB_ERROR_NO_DEVICE) { + printf("Error: fail to get status %d\n", ret); + printf("%s\n", libusb_error_name(ret)); + ret_val = ret; + } + break; + } + if (_verbose) { + printInfo("status " + dfu_dev_state_val[status.bState] + + " " + dfu_dev_status_val[status.bStatus]); + } + usleep(1000 * status.bwPollTimeout); + + switch (status.bState) { + case STATE_dfuMANIFEST_SYNC: + case STATE_dfuMANIFEST: + break; + /* need send reset */ + case STATE_dfuMANIFEST_WAIT_RESET: + ret = libusb_reset_device(dev_handle); + if (ret < 0) { + /* dfu device may be disconnected */ + if (ret != LIBUSB_ERROR_NOT_FOUND) { + printf("%s\n", libusb_error_name(ret)); + printf("ret %d\n", ret); + ret_val = -7; + } + } + must_continue = false; + break; + case STATE_dfuERROR: + printError("Error: dfuERROR state\n"); + /* before quit try to cleanup the state */ + set_state(STATE_dfuIDLE); + ret_val = -7; + must_continue = false; + break; + case STATE_dfuIDLE: + case STATE_appIDLE: + must_continue = false; + break; + } + } while (must_continue); + + return ret_val; +} + +/* After download and manifest + * device may reset itself with + * a lost of connection + * return 1 if the device is present + * 0 if the device is lost + */ +bool DFU::checkDevicePresent() +{ + struct dfu_dev curr_dfu = dfu_dev[dev_idx]; + + libusb_device_handle *handle; + handle = libusb_open_device_with_vid_pid(usb_ctx, + curr_dfu.vid, curr_dfu.pid); + if (_verbose) { + printInfo("device present ", false); + if (handle) + printInfo("True"); + else + printInfo("False"); + } + + return (handle != NULL); +} diff --git a/src/dfu.hpp b/src/dfu.hpp new file mode 100644 index 0000000..1186eaa --- /dev/null +++ b/src/dfu.hpp @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + * Copyright (c) 2021 Gwenhael Goavec-Merou +*/ + +#ifndef SRC_DFU_HPP_ +#define SRC_DFU_HPP_ + +#include + +#include +#include +#include + +#include "dfuFileParser.hpp" + +class DFU { + public: + /*! + * \brief contructor + * \param[in] verbose_lvl: verbose level 0 normal, -1 quiet, 1 verbose + */ + DFU(const std::string &filename, int verbose_lvl); + + ~DFU(); + + /*! + * \brief enumerate all USB peripherals configuration to detect DFU devices + * \return EXIT_FAILURE when something is wrong, EXIT_SUCCESS otherwise + */ + + int searchDFUDevices(); + /*! + * \brief display details about all DFU compatible devices found + */ + void displayDFU(); + + /*! + * \brief download file content + * \param[in] filename: filename to download + */ + int download(); + + private: + /** + * \brief dfu descriptor structure (not provided by libusb + */ + struct dfu_desc { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bmAttributes; + uint16_t wDetachTimeOut; + uint16_t wTransferSize; + uint16_t bcdDFUVersion; + } __attribute__((__packed__)); + + struct dfu_dev { + uint16_t vid; + uint16_t pid; + uint8_t bus; + uint8_t interface; + uint8_t device; + uint8_t path[8]; + uint32_t bMaxPacketSize0; + struct dfu_desc dfu_desc; + }; + + /* USB Device firmware Upgrade Specification, Revision 1.1 6.1.2 */ + /* p.20 */ + struct dfu_status { + uint8_t bStatus; + uint32_t bwPollTimeout; + uint8_t bState; + uint8_t iString; + }; + + /* USB Device firmware Upgrade Specification, Revision 1.1 6.1.2 */ + /* p.21 */ + enum dfu_dev_status { + STATUS_OK = 0x00, + STATUS_errTARGET = 0x01, + STATUS_errFILE = 0x02, + STATUS_errWRITE = 0x03, + STATUS_errERASE = 0x04, + STATUS_errCHECK_ERASED = 0x05, + STATUS_errPROG = 0x06, + STATUS_errVERIFY = 0x07, + STATUS_errADDRESS = 0x08, + STATUS_errNOTDONE = 0x09, + STATUS_errFIRMWARE = 0x0A, + STATUS_errVENDOR = 0x0B, + STATUS_errUSBR = 0x0C, + STATUS_errPOR = 0x0D, + STATUS_errUNKNOWN = 0x0E, + STATUS_errSTALLEDPKT = 0x0F + }; + + std::map dfu_dev_status_val = { + {STATUS_OK, "STATUS_OK"}, + {STATUS_errTARGET, "STATUS_errTARGET"}, + {STATUS_errFILE, "STATUS_errFILE"}, + {STATUS_errWRITE, "STATUS_errWRITE"}, + {STATUS_errERASE, "STATUS_errERASE"}, + {STATUS_errCHECK_ERASED, "STATUS_errCHECK_ERASED"}, + {STATUS_errPROG, "STATUS_errPROG"}, + {STATUS_errVERIFY, "STATUS_errVERIFY"}, + {STATUS_errADDRESS, "STATUS_errADDRESS"}, + {STATUS_errNOTDONE, "STATUS_errNOTDONE"}, + {STATUS_errFIRMWARE, "STATUS_errFIRMWARE"}, + {STATUS_errVENDOR, "STATUS_errVENDOR"}, + {STATUS_errUSBR, "STATUS_errUSBR"}, + {STATUS_errPOR, "STATUS_errPOR"}, + {STATUS_errUNKNOWN, "STATUS_errUNKNOWN"}, + {STATUS_errSTALLEDPKT, "STATUS_errSTALLEDPKT"} + }; + + /* p.22 */ + enum dfu_dev_state { + STATE_appIDLE = 0, + STATE_appDETACH = 1, + STATE_dfuIDLE = 2, + STATE_dfuDNLOAD_SYNC = 3, + STATE_dfuDNBUSY = 4, + STATE_dfuDNLOAD_IDLE = 5, + STATE_dfuMANIFEST_SYNC = 6, + STATE_dfuMANIFEST = 7, + STATE_dfuMANIFEST_WAIT_RESET = 8, + STATE_dfuUPLOAD_IDLE = 9, + STATE_dfuERROR = 10 + }; + + std::map dfu_dev_state_val = { + {STATE_appIDLE, "STATE_appIDLE"}, + {STATE_appDETACH, "STATE_appDETACH"}, + {STATE_dfuIDLE, "STATE_dfuIDLE"}, + {STATE_dfuDNLOAD_SYNC, "STATE_dfuDNLOAD-SYNC"}, + {STATE_dfuDNBUSY, "STATE_dfuDNBUSY"}, + {STATE_dfuDNLOAD_IDLE, "STATE_dfuDNLOAD-IDLE"}, + {STATE_dfuMANIFEST_SYNC, "STATE_dfuMANIFEST-SYNC"}, + {STATE_dfuMANIFEST, "STATE_dfuMANIFEST"}, + {STATE_dfuMANIFEST_WAIT_RESET, "STATE_dfuMANIFEST-WAIT-RESET"}, + {STATE_dfuUPLOAD_IDLE, "STATE_dfuUPLOAD-IDLE"}, + {STATE_dfuERROR, "STATE_dfuERROR"} + }; + + /*! + * \brief take control to the DFU device + * \param[in] index: device index in dfu_dev list + * \return EXIT_FAILURE when open device fails, EXIT_SUCCESS otherwise + */ + int open_DFU(int index); + /*! + * \brief release control to the DFU device + * \return EXIT_FAILURE when close fails, EXIT_SUCCESS otherwise + */ + int close_DFU(); + /*! + * \brief send detach command + * return EXIT_FAILURE when transaction fails, EXIT_SUCCESS otherwise + */ + int dfu_detach(); + /*! + * \brief read device status + * \param[out] status: struct dfu_status device payload + * return error/return code from libusb or number of bytes read/write + */ + int get_status(struct dfu_status *status); + /*! + * \brief move through DFU state from current state to newState + * \param[in] newState: targeted state + * \return -1 when something fail, 0 otherwise + */ + int set_state(char newState); + /*! + * \brief get current state without changing DFU state (6.1.5) + * \return -1 when USB transaction fail, state otherwise + */ + char get_state(); + /*! + * \brief poll status until device is in state mode + * \param[in] targeted state + * \return value < 0 when transaction fails, new state otherwise + */ + int poll_state(uint8_t state); + /*! + * \brief verify if DFU device always exist + * \return false if lost, true if present + */ + bool checkDevicePresent(); + /*! + * \brief send an IN/OUT request + */ + int send(bool out, uint8_t brequest, uint16_t wvalue, + unsigned char *data, uint16_t length); + /*! + * \brief fill specific DFU structure with extra descriptor + * \param[in] intf: interface descriptor with extra area + * \param[out] dfu_desc: DFU descriptor + * \param[in] dfu_desc_size: DFU descriptor structure size + * \return -1 if extra len is too small, 0 otherwise + * */ + int parseDFUDescriptor(const struct libusb_interface_descriptor *intf, + uint8_t *dfu_desc, int dfu_desc_size); + /*! + * \brief search, for the specified device, if it has a DFU interface + * \param[in] dev: USB device + * \param[in] desc: USB device descriptor + * \return 1 when can't read config, 0 otherwise + */ + int searchIfDFU(struct libusb_device *dev, + struct libusb_device_descriptor *desc); + + bool _verbose; /**< display more message */ + bool _quiet; /**< don't use progressBar */ + std::vector dfu_dev; /**< available dfu devices */ + int dev_idx; /**< device index in dfu_dev */ + uint16_t _vid; /**< device Vendor ID */ + uint16_t _pid; /**< device Product ID */ + struct libusb_context *usb_ctx; /**< usb context */ + libusb_device_handle * dev_handle; /**< current device handle */ + int curr_intf; /**< device interface to use */ + int transaction; /**< download transaction ID */ + + DFUFileParser *_bit; /**< dfu file to load */ +}; + +#endif // SRC_DFU_HPP_ diff --git a/src/main.cpp b/src/main.cpp index 9e52025..a957cd6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -29,6 +29,7 @@ #include "board.hpp" #include "cable.hpp" #include "device.hpp" +#include "dfu.hpp" #include "display.hpp" #include "efinix.hpp" #include "ftdispi.hpp" @@ -61,6 +62,7 @@ struct arguments { Device::prog_type_t prg_type; bool is_list_command; bool spi; + bool dfu; string file_type; string fpga_part; string probe_firmware; @@ -79,7 +81,7 @@ int main(int argc, char **argv) /* command line args. */ struct arguments args = {0, false, false, 0, "", "", "-", "", -1, 6000000, "-", - false, false, false, false, Device::WR_SRAM, false, false, "", + false, false, false, false, Device::WR_SRAM, false, false, false, "", "", "", -1}; /* parse arguments */ try { @@ -229,6 +231,36 @@ int main(int argc, char **argv) return EXIT_SUCCESS; } + /* ------------------- */ + /* DFU access */ + /* ------------------- */ + if (args.dfu || (board && board->mode == COMM_DFU)) { + /* try to init DFU probe */ + DFU *dfu = NULL; + try { + dfu = new DFU(args.bit_file, args.verbose); + } catch (std::exception &e) { + printError("DFU init failed with: " + string(e.what())); + return EXIT_FAILURE; + } + /* if verbose or detect: display device */ + if (args.verbose > 0 || args.detect) + dfu->displayDFU(); + + /* if detect: stop */ + if (args.detect) + return EXIT_SUCCESS; + + try { + dfu->download(); + } catch (std::exception &e) { + printError("DFU download failed with: " + string(e.what())); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; + } + /* jtag base */ Jtag *jtag; try { @@ -422,6 +454,7 @@ int parse_opt(int argc, char **argv, struct arguments *args, jtag_pins_conf_t *p #endif ("detect", "detect FPGA", cxxopts::value(args->detect)) + ("dfu", "DFU mode", cxxopts::value(args->dfu)) ("file-type", "provides file type instead of let's deduced by using extension", cxxopts::value(args->file_type)) ("fpga-part", "fpga model flavor + package", cxxopts::value(args->fpga_part))