add proof of concept / draft for DFU protocol. Add orangeCrab in DFU mode

This commit is contained in:
Gwenhael Goavec-Merou 2021-06-20 10:25:49 +02:00
parent dddfcbc973
commit 827767b99f
7 changed files with 1000 additions and 2 deletions

View File

@ -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

View File

@ -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

View File

@ -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 <std::string, target_board_t> board_list = {
JTAG_BOARD("acornCle215", "xc7a200tsbg484", "", 0, 0),
@ -116,6 +119,7 @@ static std::map <std::string, target_board_t> 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),

View File

@ -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 <std::string, cable_t> 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}}},

727
src/dfu.cpp Normal file
View File

@ -0,0 +1,727 @@
// SPDX-License-Identifier: GPL-3.0-or-later
/*
* Copyright (c) 2021 Gwenhael Goavec-Merou <gwenhael.goavec-merou@trabucayre.com>
*/
#include <libusb.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <unistd.h>
#include <iostream>
#include <map>
#include <stdexcept>
#include <string>
#include <vector>
#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<uint8_t *>(&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);
}

227
src/dfu.hpp Normal file
View File

@ -0,0 +1,227 @@
// SPDX-License-Identifier: GPL-3.0-or-later
/*
* Copyright (c) 2021 Gwenhael Goavec-Merou <gwenhael.goavec-merou@trabucayre.com>
*/
#ifndef SRC_DFU_HPP_
#define SRC_DFU_HPP_
#include <libusb.h>
#include <map>
#include <string>
#include <vector>
#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<uint8_t, std::string> 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 <uint8_t, std::string> 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<struct dfu_dev> 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_

View File

@ -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<bool>(args->detect))
("dfu", "DFU mode", cxxopts::value<bool>(args->dfu))
("file-type", "provides file type instead of let's deduced by using extension",
cxxopts::value<string>(args->file_type))
("fpga-part", "fpga model flavor + package", cxxopts::value<string>(args->fpga_part))