diff --git a/include/led-drivers/spi/ProviderSpiFtdi.h b/include/led-drivers/spi/ProviderSpiFtdi.h index 2f5154f5e..700c7b300 100644 --- a/include/led-drivers/spi/ProviderSpiFtdi.h +++ b/include/led-drivers/spi/ProviderSpiFtdi.h @@ -4,6 +4,8 @@ #include #include + +#include #include #include @@ -23,6 +25,7 @@ class ProviderSpiFtdi : public QObject, public ProviderSpiInterface { HMODULE _dllHandle; FT_HANDLE _deviceHandle; + std::vector _writeCommand; PTR_FT_ListDevices _fun_FT_ListDevices; PTR_FT_OpenEx _fun_FT_OpenEx; diff --git a/include/led-drivers/spi/ProviderSpiLibFtdi.h b/include/led-drivers/spi/ProviderSpiLibFtdi.h index 6d7f9c15e..2489c5fba 100644 --- a/include/led-drivers/spi/ProviderSpiLibFtdi.h +++ b/include/led-drivers/spi/ProviderSpiLibFtdi.h @@ -3,11 +3,15 @@ #include #include +#include + #include #include typedef struct ftdi_context* (*PTR_ftdi_new)(void); typedef int (*PTR_ftdi_usb_open_bus_addr)(struct ftdi_context* ftdi, uint8_t bus, uint8_t addr); +typedef int (*PTR_ftdi_usb_open_string)(struct ftdi_context* ftdi, const char* description); +typedef int (*PTR_ftdi_usb_get_strings)(struct ftdi_context* ftdi, struct libusb_device* dev, char* manufacturer, int mnf_len, char* description, int desc_len, char* serial, int serial_len); typedef void (*PTR_ftdi_free)(struct ftdi_context* ftdi); typedef int (*PTR_ftdi_usb_reset)(struct ftdi_context* ftdi); typedef int (*PTR_ftdi_set_baudrate)(struct ftdi_context* ftdi, int baudrate); @@ -28,9 +32,12 @@ class ProviderSpiLibFtdi final : public QObject, public ProviderSpiInterface { void* _dllHandle; struct ftdi_context* _deviceHandle; + std::vector _writeCommand; PTR_ftdi_new _fun_ftdi_new; PTR_ftdi_usb_open_bus_addr _fun_ftdi_usb_open_bus_addr; + PTR_ftdi_usb_open_string _fun_ftdi_usb_open_string; + PTR_ftdi_usb_get_strings _fun_ftdi_usb_get_strings; PTR_ftdi_free _fun_ftdi_free; PTR_ftdi_usb_reset _fun_ftdi_usb_reset; PTR_ftdi_set_baudrate _fun_ftdi_set_baudrate; diff --git a/sources/led-drivers/spi/ProviderSpi.cpp b/sources/led-drivers/spi/ProviderSpi.cpp index 5305d47c1..05e65e3d9 100644 --- a/sources/led-drivers/spi/ProviderSpi.cpp +++ b/sources/led-drivers/spi/ProviderSpi.cpp @@ -85,10 +85,16 @@ bool ProviderSpi::init(QJsonObject deviceConfig) // Initialise sub-class if (LedDevice::init(deviceConfig)) { - bool isInt = false; - #ifdef ENABLE_SPI_FTDI - deviceConfig["output"].toString().toLong(&isInt, 10); - if (isInt) + bool isFtdi = false; + #ifdef ENABLE_SPI_FTDI + { + QString output = deviceConfig["output"].toString(); + bool isInt = false; + output.toLong(&isInt, 10); + // Use FTDI provider for numeric bus:addr locations or ftdi_usb_open_string identifiers + // (e.g. "s:0x0403:0x6014:serial", "i:0x0403:0x6014", "d:/dev/...") + isFtdi = isInt || output.startsWith("s:") || output.startsWith("i:") || output.startsWith("d:"); + if (isFtdi) { #ifdef WIN32 _provider = std::make_unique(_log); @@ -96,10 +102,11 @@ bool ProviderSpi::init(QJsonObject deviceConfig) _provider = std::make_unique(_log); #endif } + } #endif #if !defined(WIN32) && !defined(__APPLE__) - if (!isInt) + if (!isFtdi) { _provider = std::make_unique(_log); } diff --git a/sources/led-drivers/spi/ProviderSpiFtdi.cpp b/sources/led-drivers/spi/ProviderSpiFtdi.cpp index 5dc19c7ec..86ca93c33 100644 --- a/sources/led-drivers/spi/ProviderSpiFtdi.cpp +++ b/sources/led-drivers/spi/ProviderSpiFtdi.cpp @@ -25,7 +25,9 @@ * SOFTWARE. */ +#include #include +#include #include #include @@ -220,7 +222,7 @@ QString ProviderSpiFtdi::open() if (_fun_FT_Write(_deviceHandle, command.data(), static_cast(command.size()), &dwNumBytesSent) != FT_OK) { - error = "Cannot initilize SPI interface"; + error = "Cannot initialize SPI interface"; } } @@ -246,34 +248,43 @@ int ProviderSpiFtdi::close() int ProviderSpiFtdi::writeBytes(unsigned size, const uint8_t* data) { + if (size == 0 || size > 65536) + { + return -1; + } + DWORD dwNumBytesSent = 0; - std::vector command; + _writeCommand.clear(); + if (_writeCommand.capacity() < 3 + 3 + size + 3) + { + _writeCommand.reserve(3 + 3 + size + 3); + } // cs & clock low - command.push_back(0x80); - command.push_back(0); - command.push_back(0x08 | 0x02 | 0x01); - _fun_FT_Write(_deviceHandle, command.data(), static_cast(command.size()), &dwNumBytesSent); - - command.push_back(0x11); - command.push_back((size - 1) & 0xFF); - command.push_back(((size - 1) >> 8) & 0xFF); - _fun_FT_Write(_deviceHandle, command.data(), static_cast(command.size()), &dwNumBytesSent); - if (_fun_FT_Write(_deviceHandle, const_cast(data), size, &dwNumBytesSent) != FT_OK) + _writeCommand.push_back(0x80); + _writeCommand.push_back(0); + _writeCommand.push_back(0x08 | 0x02 | 0x01); + + // MPSSE DO_WRITE command + length + _writeCommand.push_back(0x11); + _writeCommand.push_back((size - 1) & 0xFF); + _writeCommand.push_back(((size - 1) >> 8) & 0xFF); + + // data payload + _writeCommand.insert(_writeCommand.end(), data, data + size); + + // cs high + _writeCommand.push_back(0x80); + _writeCommand.push_back(0x08); + _writeCommand.push_back(0x08 | 0x02 | 0x01); + + if (_fun_FT_Write(_deviceHandle, _writeCommand.data(), static_cast(_writeCommand.size()), &dwNumBytesSent) != FT_OK) { Error(_log, "The FTDI device reports error while writing"); return -1; } - // cs high - command.clear(); - command.push_back(0x80); - command.push_back(0x08); - command.push_back(0x08 | 0x02 | 0x01); - _fun_FT_Write(_deviceHandle, command.data(), static_cast(command.size()), &dwNumBytesSent); - - - return dwNumBytesSent; + return size; } int ProviderSpiFtdi::getRate() @@ -308,13 +319,11 @@ QJsonObject ProviderSpiFtdi::discover(const QJsonObject& /*params*/) QJsonArray deviceList; QStringList files; - for (DWORD i = 0, count = std::min(numDevs, DWORD(std::size(deviceIds))); i < count; ++i) - { + for (DWORD i = 0, count = std::min(numDevs, DWORD(std::size(deviceIds))); i < count; i++) deviceList.push_back(QJsonObject{ {"value", QJsonValue(static_cast(deviceIds[i]))}, {"name", QString("FTDI SPI device location: %1").arg(QString::number(deviceIds[i]))} }); - } devicesDiscovered.insert("devices", deviceList); diff --git a/sources/led-drivers/spi/ProviderSpiLibFtdi.cpp b/sources/led-drivers/spi/ProviderSpiLibFtdi.cpp index 8e638a7b9..7ea656659 100644 --- a/sources/led-drivers/spi/ProviderSpiLibFtdi.cpp +++ b/sources/led-drivers/spi/ProviderSpiLibFtdi.cpp @@ -25,30 +25,22 @@ * SOFTWARE. */ -#include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include +#include #include #include namespace { +#ifdef __APPLE__ + constexpr auto* LIBFTDI_CANON = "libftdi1.dylib"; + constexpr auto* LIBFTDI_ALT = "libftdi1.1.dylib"; +#else constexpr auto* LIBFTDI_CANON = "libftdi1.so"; constexpr auto* LIBFTDI_ALT = "libftdi1.so.2"; +#endif } ProviderSpiLibFtdi::ProviderSpiLibFtdi(const LoggerName& logger) @@ -57,6 +49,8 @@ ProviderSpiLibFtdi::ProviderSpiLibFtdi(const LoggerName& logger) _deviceHandle(nullptr), _fun_ftdi_new(nullptr), _fun_ftdi_usb_open_bus_addr(nullptr), + _fun_ftdi_usb_open_string(nullptr), + _fun_ftdi_usb_get_strings(nullptr), _fun_ftdi_free(nullptr), _fun_ftdi_usb_reset(nullptr), _fun_ftdi_set_baudrate(nullptr), @@ -103,6 +97,8 @@ bool ProviderSpiLibFtdi::loadLibrary() LOAD_PROC(ftdi_new); LOAD_PROC(ftdi_usb_open_bus_addr); + LOAD_PROC(ftdi_usb_open_string); + LOAD_PROC(ftdi_usb_get_strings); LOAD_PROC(ftdi_free); LOAD_PROC(ftdi_usb_reset); LOAD_PROC(ftdi_set_baudrate); @@ -167,27 +163,42 @@ QString ProviderSpiLibFtdi::open() { QString error; + if ((_deviceHandle = _fun_ftdi_new()) == nullptr) + { + return "libFTDI ftdi_new has failed"; + } + bool isInt = false; long long deviceLocation = _deviceName.toLong(&isInt, 10); - if (!isInt) + if (isInt) { - return "The device name is not a FTDI path (must be a number)"; - } + Debug(_log, "Opening FTDI device by bus:addr location: {:d} (bus={:d}, addr={:d})", + deviceLocation, (int)((deviceLocation >> 8) & 0xff), (int)(deviceLocation & 0xff)); - if ((_deviceHandle = _fun_ftdi_new()) == nullptr) - { - return "libFTDI ftdi_new has failed"; - } + if (_fun_ftdi_usb_open_bus_addr(_deviceHandle, (deviceLocation >> 8) & 0xff, (deviceLocation) & 0xff) < 0) + { + Error(_log, "libFTDI ftdi_usb_open_bus_addr has failed: {:s}", _fun_ftdi_get_error_string(_deviceHandle)); + + _fun_ftdi_free(_deviceHandle); + _deviceHandle = nullptr; - if (_fun_ftdi_usb_open_bus_addr(_deviceHandle, (deviceLocation >> 8) & 0xff, (deviceLocation) & 0xff) < 0) + return "libFTDI ftdi_usb_open_bus_addr has failed"; + } + } + else { - Error(_log, "libFTDI ftdi_usb_open_bus_addr has failed: {:s}", _fun_ftdi_get_error_string(_deviceHandle)); + Debug(_log, "Opening FTDI device by string identifier: {:s}", _deviceName.toUtf8().constData()); - _fun_ftdi_free(_deviceHandle); - _deviceHandle = nullptr; + if (_fun_ftdi_usb_open_string(_deviceHandle, _deviceName.toUtf8().constData()) < 0) + { + Error(_log, "libFTDI ftdi_usb_open_string has failed: {:s}", _fun_ftdi_get_error_string(_deviceHandle)); + + _fun_ftdi_free(_deviceHandle); + _deviceHandle = nullptr; - return "libFTDI ftdi_usb_open_bus_addr has failed"; + return QString("libFTDI ftdi_usb_open_string has failed for '%1'").arg(_deviceName); + } } Debug(_log, "Initializing MPSSE interface..."); @@ -204,7 +215,7 @@ QString ProviderSpiLibFtdi::open() if (error.isEmpty() && _fun_ftdi_write_data_set_chunksize(_deviceHandle, 65535) < 0) { - error = "libFTDI ftdi_usb_reset did not return properly"; + error = "libFTDI ftdi_write_data_set_chunksize did not return properly"; } if (error.isEmpty() && _fun_ftdi_set_event_char(_deviceHandle, 0, false) < 0) @@ -257,7 +268,7 @@ QString ProviderSpiLibFtdi::open() if (_fun_ftdi_write_data(_deviceHandle, command.data(), command.size()) < 0) { - error = "Cannot initilize SPI interface"; + error = "Cannot initialize SPI interface"; } } @@ -285,31 +296,40 @@ int ProviderSpiLibFtdi::close() int ProviderSpiLibFtdi::writeBytes(unsigned size, const uint8_t* data) { - std::vector command; - - // cs & clock low - command.push_back(0x80); - command.push_back(0); - command.push_back(0x08 | 0x02 | 0x01); - _fun_ftdi_write_data(_deviceHandle, command.data(), command.size()); - - command.push_back(0x11); - command.push_back((size - 1) & 0xFF); - command.push_back(((size - 1) >> 8) & 0xFF); - _fun_ftdi_write_data(_deviceHandle, command.data(), command.size()); - if (_fun_ftdi_write_data(_deviceHandle, const_cast(data), size) < static_cast(size)) + if (size == 0 || size > 65536) { - Error(_log, "The FTDI device reports error while writing"); return -1; } + _writeCommand.clear(); + if (_writeCommand.capacity() < 3 + 3 + size + 3) + { + _writeCommand.reserve(3 + 3 + size + 3); + } + + // cs & clock low + _writeCommand.push_back(0x80); + _writeCommand.push_back(0); + _writeCommand.push_back(0x08 | 0x02 | 0x01); + + // MPSSE DO_WRITE command + length + _writeCommand.push_back(0x11); + _writeCommand.push_back((size - 1) & 0xFF); + _writeCommand.push_back(((size - 1) >> 8) & 0xFF); + + // data payload + _writeCommand.insert(_writeCommand.end(), data, data + size); + // cs high - command.clear(); - command.push_back(0x80); - command.push_back(0x08); - command.push_back(0x08 | 0x02 | 0x01); - _fun_ftdi_write_data(_deviceHandle, command.data(), command.size()); + _writeCommand.push_back(0x80); + _writeCommand.push_back(0x08); + _writeCommand.push_back(0x08 | 0x02 | 0x01); + if (_fun_ftdi_write_data(_deviceHandle, _writeCommand.data(), static_cast(_writeCommand.size())) < static_cast(_writeCommand.size())) + { + Error(_log, "The FTDI device reports error while writing"); + return -1; + } return size; } @@ -354,15 +374,34 @@ QJsonObject ProviderSpiLibFtdi::discover(const QJsonObject& /*params*/) { if (numDevs > 0) { - QJsonArray deviceList; + QJsonArray deviceList; struct ftdi_device_list* curDev = devlist; while (curDev) { long deviceLocation = ((curDev->dev->bus_number & 0xff) << 8) | (curDev->dev->device_address & 0xff); + + char manufacturer[128] = {0}; + char description[128] = {0}; + char serial[128] = {0}; + _fun_ftdi_usb_get_strings(ftdic, curDev->dev, manufacturer, sizeof(manufacturer), description, sizeof(description), serial, sizeof(serial)); + + QString displayName = QString("libFTDI SPI device location: %1").arg(QString::number(deviceLocation)); + if (serial[0] != '\0') + displayName += QString(" (serial: %1)").arg(serial); + if (description[0] != '\0') + displayName += QString(" [%1]").arg(description); + + QString stableId; + if (serial[0] != '\0') + stableId = QString("s:0x%1:0x%2:%3") + .arg(curDev->dev->device_descriptor.idVendor, 4, 16, QChar('0')) + .arg(curDev->dev->device_descriptor.idProduct, 4, 16, QChar('0')) + .arg(serial); + deviceList.push_back(QJsonObject{ - {"value", QJsonValue((qint64)deviceLocation)}, - { "name", QString("libFTDI SPI device location: %1").arg(QString::number(deviceLocation)) } }); + {"value", stableId.isEmpty() ? QJsonValue((qint64)deviceLocation) : QJsonValue(stableId)}, + {"name", displayName}}); curDev = curDev->next; }