diff options
author | Daniel Schürmann <daschuer@mixxx.org> | 2023-05-31 14:24:54 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-05-31 14:24:54 -0800 |
commit | 53920a204366c4dce295974a92170c6b004bb499 (patch) | |
tree | b2694cd4fb1efeab9a9326e5628bb50ec0d4163d | |
parent | f031103207375602e69a3eb822e44e1976eb1fe2 (diff) | |
parent | af6e60ac48b20f5d7b097976680992350bce3fac (diff) |
Merge pull request #11326 from JoergAtGithub/hid_fifo_mode3
FIFO mode option for HID OutputReports
-rw-r--r-- | CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/controllers/hid/hidcontroller.h | 33 | ||||
-rw-r--r-- | src/controllers/hid/hidioglobaloutputreportfifo.cpp | 94 | ||||
-rw-r--r-- | src/controllers/hid/hidioglobaloutputreportfifo.h | 31 | ||||
-rw-r--r-- | src/controllers/hid/hidiooutputreport.cpp | 51 | ||||
-rw-r--r-- | src/controllers/hid/hidiooutputreport.h | 8 | ||||
-rw-r--r-- | src/controllers/hid/hidiothread.cpp | 26 | ||||
-rw-r--r-- | src/controllers/hid/hidiothread.h | 5 |
8 files changed, 210 insertions, 39 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 47f96f1ff7..883430f564 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3028,6 +3028,7 @@ if(HID) target_sources(mixxx-lib PRIVATE src/controllers/hid/hidcontroller.cpp src/controllers/hid/hidiothread.cpp + src/controllers/hid/hidioglobaloutputreportfifo.cpp src/controllers/hid/hidiooutputreport.cpp src/controllers/hid/hiddevice.cpp src/controllers/hid/hidenumerator.cpp diff --git a/src/controllers/hid/hidcontroller.h b/src/controllers/hid/hidcontroller.h index d735fc8db6..6f7f81108f 100644 --- a/src/controllers/hid/hidcontroller.h +++ b/src/controllers/hid/hidcontroller.h @@ -70,33 +70,52 @@ class HidControllerJSProxy : public ControllerJSProxy { /// @brief Sends HID OutputReport to HID device /// @param dataList Data to send as list of bytes /// @param length Unused but mandatory argument - /// @param reportID 1...255 for HID devices that uses ReportIDs - or 0 for devices, which don't use ReportIDs - /// @param resendUnchangedReport If set, the report will also be send, if the data are unchanged since last sending + /// @param reportID 1...255 for HID devices that uses ReportIDs - or 0 for + /// devices, which don't use ReportIDs + /// @param useNonSkippingFIFO (optional) + /// Same as argument useNonSkippingFIFO of the sendOutputReport function, + /// which is documented below Q_INVOKABLE void send(const QList<int>& dataList, unsigned int length, quint8 reportID, - bool resendUnchangedReport = false) { + bool useNonSkippingFIFO = false) { Q_UNUSED(length); QByteArray dataArray; dataArray.reserve(dataList.size()); for (int datum : dataList) { dataArray.append(datum); } - sendOutputReport(reportID, dataArray, resendUnchangedReport); + sendOutputReport(reportID, dataArray, useNonSkippingFIFO); } /// @brief Sends an OutputReport to HID device /// @param reportID 1...255 for HID devices that uses ReportIDs - or 0 for devices, which don't use ReportIDs /// @param dataArray Data to send as byte array (Javascript type Uint8Array) - /// @param resendUnchangedReport If set, the report will also be send, if the data are unchanged since last sending + /// @param useNonSkippingFIFO (optional) + /// - False (default): + /// - Reports with identical data will be sent only once. + /// - If reports were superseded by newer data before they could be sent, + /// the oudated data will be skipped. + /// - This mode works for all USB HID class compatible reports, + /// in these each field represents the state of a control (e.g. an LED). + /// - This mode works best in overload situations, where more reports + /// are to be sent, than can be processed. + /// - True: + /// - The report will not be skipped under any circumstances, + /// except FIFO memory overflow. + /// - All reports with useNonSkippingFIFO set True will be send before + /// any cached report with useNonSkippingFIFO set False. + /// - All reports with useNonSkippingFIFO set True will be send in + /// strict First In / First Out (FIFO) order. + /// - Limit the use of this mode to the places, where it is really necessary. Q_INVOKABLE void sendOutputReport(quint8 reportID, const QByteArray& dataArray, - bool resendUnchangedReport = false) { + bool useNonSkippingFIFO = false) { VERIFY_OR_DEBUG_ASSERT(m_pHidController->m_pHidIoThread) { return; } m_pHidController->m_pHidIoThread->updateCachedOutputReportData( - reportID, dataArray, resendUnchangedReport); + reportID, dataArray, useNonSkippingFIFO); } /// @brief getInputReport receives an InputReport from the HID device on request. diff --git a/src/controllers/hid/hidioglobaloutputreportfifo.cpp b/src/controllers/hid/hidioglobaloutputreportfifo.cpp new file mode 100644 index 0000000000..c8cb0c958f --- /dev/null +++ b/src/controllers/hid/hidioglobaloutputreportfifo.cpp @@ -0,0 +1,94 @@ +#include "controllers/hid/hidioglobaloutputreportfifo.h" + +#include <hidapi.h> + +#include "controllers/defs_controllers.h" +#include "controllers/hid/legacyhidcontrollermappingfilehandler.h" +#include "util/compatibility/qbytearray.h" +#include "util/compatibility/qmutex.h" +#include "util/string.h" +#include "util/time.h" +#include "util/trace.h" + +namespace { +constexpr size_t kMaxHidErrorMessageSize = 512; +constexpr size_t kSizeOfFifoInReports = 32; +} // namespace + +HidIoGlobalOutputReportFifo::HidIoGlobalOutputReportFifo() + : m_fifoQueue(kSizeOfFifoInReports) { +} + +void HidIoGlobalOutputReportFifo::addReportDatasetToFifo(const quint8 reportId, + const QByteArray& reportData, + const mixxx::hid::DeviceInfo& deviceInfo, + const RuntimeLoggingCategory& logOutput) { + // First byte must always be the ReportID-Byte + QByteArray report(reportData); + report.prepend(reportId); // In Qt6 this is a very fast operation without reallocation + + // Swap report to lockless FIFO queue + bool success = m_fifoQueue.try_emplace(std::move(report)); + + // Handle the case, that the FIFO queue is full - which is an error case + if (!success) { + // If the FIFO is full, we skip the report dataset even + // in non-skipping mode, to keep the controller mapping thread + // responsive for InputReports from the controller. + // Alternative would be to block processing of the controller + // mapping thread, until the FIFO has space again. + qCWarning(logOutput) + << "FIFO overflow: Unable to add OutputReport " << reportId + << "to the global cache for non-skipping sending of OututReports for" + << deviceInfo.formatName(); + } +} + +bool HidIoGlobalOutputReportFifo::sendNextReportDataset(QMutex* pHidDeviceAndPollMutex, + hid_device* pHidDevice, + const mixxx::hid::DeviceInfo& deviceInfo, + const RuntimeLoggingCategory& logOutput) { + auto startOfHidWrite = mixxx::Time::elapsed(); + + QByteArray* pFront = m_fifoQueue.front(); + + if (pFront == nullptr) { + // No data in FIFO to be send + // Return with false, to signal the caller, that no time consuming IO + // operation was ncessary + return false; + } + + // Array containing the ReportID byte followed by the data to be send + QByteArray reportToSend(std::move(*pFront)); + m_fifoQueue.pop(); + + auto hidDeviceLock = lockMutex(pHidDeviceAndPollMutex); + + // hid_write can take several milliseconds, because hidapi synchronizes + // the asyncron HID communication from the OS + int result = hid_write(pHidDevice, + reinterpret_cast<const unsigned char*>(reportToSend.constData()), + reportToSend.size()); + if (result == -1) { + qCWarning(logOutput) << "Unable to send data to" << deviceInfo.formatName() << ":" + << mixxx::convertWCStringToQString( + hid_error(pHidDevice), + kMaxHidErrorMessageSize); + } + + hidDeviceLock.unlock(); + + if (result != -1) { + qCDebug(logOutput) << "t:" << startOfHidWrite.formatMillisWithUnit() + << " " << result << "bytes (including ReportID of" + << static_cast<quint8>(reportToSend[0]) + << ") sent from non-skipping FIFO - Needed: " + << (mixxx::Time::elapsed() - startOfHidWrite) + .formatMicrosWithUnit(); + } + + // Return with true, to signal the caller, that the time consuming hid_write + // operation was executed + return true; +} diff --git a/src/controllers/hid/hidioglobaloutputreportfifo.h b/src/controllers/hid/hidioglobaloutputreportfifo.h new file mode 100644 index 0000000000..d9735b818e --- /dev/null +++ b/src/controllers/hid/hidioglobaloutputreportfifo.h @@ -0,0 +1,31 @@ +#pragma once + +#include "controllers/controller.h" +#include "controllers/hid/hiddevice.h" +#include "rigtorp/SPSCQueue.h" +#include "util/duration.h" + +/// Stores and sends OutputReports (independent of the ReportID) in First In / +/// First Out (FIFO) order +class HidIoGlobalOutputReportFifo { + public: + HidIoGlobalOutputReportFifo(); + + /// Caches new OutputReport to the FIFO, which will later be send by the IO thread + void addReportDatasetToFifo(const quint8 reportId, + const QByteArray& reportData, + const mixxx::hid::DeviceInfo& deviceInfo, + const RuntimeLoggingCategory& logOutput); + + /// Sends the next OutputReport from FIFO to the HID device, + /// when if any report is cached in FIFO. + /// Returns true if a time consuming hid_write operation was executed. + bool sendNextReportDataset(QMutex* pHidDeviceAndPollMutex, + hid_device* pHidDevice, + const mixxx::hid::DeviceInfo& deviceInfo, + const RuntimeLoggingCategory& logOutput); + + private: + // Lockless FIFO queue + rigtorp::SPSCQueue<QByteArray> m_fifoQueue; +}; diff --git a/src/controllers/hid/hidiooutputreport.cpp b/src/controllers/hid/hidiooutputreport.cpp index 80765177ac..2cfd7f9587 100644 --- a/src/controllers/hid/hidiooutputreport.cpp +++ b/src/controllers/hid/hidiooutputreport.cpp @@ -11,7 +11,7 @@ namespace { constexpr int kReportIdSize = 1; -constexpr int kMaxHidErrorMessageSize = 512; +constexpr size_t kMaxHidErrorMessageSize = 512; } // namespace HidIoOutputReport::HidIoOutputReport( @@ -29,7 +29,8 @@ HidIoOutputReport::HidIoOutputReport( void HidIoOutputReport::updateCachedData(const QByteArray& data, const mixxx::hid::DeviceInfo& deviceInfo, const RuntimeLoggingCategory& logOutput, - bool resendUnchangedReport) { + HidIoGlobalOutputReportFifo* pGlobalOutputReportFifo, + bool useNonSkippingFIFO) { auto cacheLock = lockMutex(&m_cachedDataMutex); if (!m_lastCachedDataSize) { @@ -37,27 +38,32 @@ void HidIoOutputReport::updateCachedData(const QByteArray& data, m_lastCachedDataSize = data.size(); } else { - if (m_possiblyUnsentDataCached) { + if (m_possiblyUnsentDataCached && !useNonSkippingFIFO) { qCDebug(logOutput) << "t:" << mixxx::Time::elapsed().formatMillisWithUnit() - << "Skipped superseded OutputReport" - << deviceInfo.formatName() << "serial #" - << deviceInfo.serialNumber() << "(Report ID" - << m_reportId << ")"; + << "skipped superseded OutputReport data for ReportID" + << m_reportId; } // The size of an HID report is defined in a HID device and can't vary at runtime if (m_lastCachedDataSize != data.size()) { - qCWarning(logOutput) << "Size of report (with Report ID" - << m_reportId << ") changed from" - << m_lastCachedDataSize - << "to" << data.size() << "for" - << deviceInfo.formatName() << "serial #" - << deviceInfo.serialNumber() - << "- This indicates a bug in the mapping code!"; + qCWarning(logOutput) + << "Size of OutputReport ( with ReportID" << m_reportId + << ") changed from" << m_lastCachedDataSize << "to" + << data.size() + << "- This indicates a bug in the mapping code!"; m_lastCachedDataSize = data.size(); } } + // m_possiblyUnsentDataCached must be set while m_cachedDataMutex is locked + // This step covers the case that data for the report are cached in skipping mode, + // succeed by a non-skipping send of the same report + if (useNonSkippingFIFO) { + m_possiblyUnsentDataCached = false; + m_lastSentData.clear(); + return; + } + // Deep copy with reusing the already allocated heap memory // The first byte with the ReportID is not overwritten qByteArrayReplaceWithPositionAndSize(&m_cachedData, @@ -66,7 +72,6 @@ void HidIoOutputReport::updateCachedData(const QByteArray& data, data.constData(), data.size()); m_possiblyUnsentDataCached = true; - m_resendUnchangedReport = resendUnchangedReport; } bool HidIoOutputReport::sendCachedData(QMutex* pHidDeviceAndPollMutex, @@ -82,7 +87,7 @@ bool HidIoOutputReport::sendCachedData(QMutex* pHidDeviceAndPollMutex, return false; } - if (!(m_resendUnchangedReport || m_lastSentData.compare(m_cachedData))) { + if (!m_lastSentData.compare(m_cachedData)) { // An HID OutputReport can contain only HID OutputItems. // HID OutputItems are defined to represent the state of one or more similar controls or LEDs. // Only HID Feature items may be attributes of other items. @@ -96,10 +101,8 @@ bool HidIoOutputReport::sendCachedData(QMutex* pHidDeviceAndPollMutex, cacheLock.unlock(); qCDebug(logOutput) << "t:" << startOfHidWrite.formatMillisWithUnit() - << " Skipped identical Output Report for" - << deviceInfo.formatName() << "serial #" - << deviceInfo.serialNumber() << "(Report ID" - << m_reportId << ")"; + << "Skipped sending identical OutputReport data from cache for ReportID" + << m_reportId; // Return with false, to signal the caller, that no time consuming IO operation was necessary return false; @@ -123,7 +126,7 @@ bool HidIoOutputReport::sendCachedData(QMutex* pHidDeviceAndPollMutex, reinterpret_cast<const unsigned char*>(m_lastSentData.constData()), m_lastSentData.size()); if (result == -1) { - qCWarning(logOutput) << "Unable to send data to" << deviceInfo.formatName() << ":" + qCWarning(logOutput) << "Unable to send data to device :" << mixxx::convertWCStringToQString( hid_error(pHidDevice), kMaxHidErrorMessageSize); @@ -147,9 +150,9 @@ bool HidIoOutputReport::sendCachedData(QMutex* pHidDeviceAndPollMutex, } qCDebug(logOutput) << "t:" << startOfHidWrite.formatMillisWithUnit() << " " - << result << "bytes sent to" << deviceInfo.formatName() - << "serial #" << deviceInfo.serialNumber() - << "(including report ID of" << m_reportId << ") - Needed: " + << result << "bytes ( including ReportID of" + << static_cast<quint8>(m_reportId) + << ") sent from skipping cache - Needed:" << (mixxx::Time::elapsed() - startOfHidWrite).formatMicrosWithUnit(); // Return with true, to signal the caller, that the time consuming hid_write operation was executed diff --git a/src/controllers/hid/hidiooutputreport.h b/src/controllers/hid/hidiooutputreport.h index 61bd9bd2df..e185410d58 100644 --- a/src/controllers/hid/hidiooutputreport.h +++ b/src/controllers/hid/hidiooutputreport.h @@ -2,6 +2,7 @@ #include "controllers/controller.h" #include "controllers/hid/hiddevice.h" +#include "controllers/hid/hidioglobaloutputreportfifo.h" #include "util/compatibility/qmutex.h" #include "util/duration.h" @@ -11,10 +12,10 @@ class HidIoOutputReport { /// Caches new report data, which will later send by the IO thread void updateCachedData(const QByteArray& data, - const mixxx::hid::DeviceInfo& deviceInfo, const RuntimeLoggingCategory& logOutput, - bool resendUnchangedReport); + HidIoGlobalOutputReportFifo* pGlobalOutputReportFifo, + bool useNonSkippingFIFO); /// Sends the OutputReport to the HID device, when changed data are cached. /// Returns true if a time consuming hid_write operation was executed. @@ -28,12 +29,11 @@ class HidIoOutputReport { QByteArray m_lastSentData; /// Mutex must be locked when reading/writing m_cachedData - /// or m_possiblyUnsentDataCached, m_resendUnchangedReport + /// or m_possiblyUnsentDataCached QMutex m_cachedDataMutex; QByteArray m_cachedData; bool m_possiblyUnsentDataCached; - bool m_resendUnchangedReport; /// Due to swapping of the QbyteArrays, we need to store /// this information independent of the QBytearray size diff --git a/src/controllers/hid/hidiothread.cpp b/src/controllers/hid/hidiothread.cpp index 6b295f64d9..dcde35af2d 100644 --- a/src/controllers/hid/hidiothread.cpp +++ b/src/controllers/hid/hidiothread.cpp @@ -13,7 +13,7 @@ namespace { constexpr int kReportIdSize = 1; -constexpr int kMaxHidErrorMessageSize = 512; +constexpr size_t kMaxHidErrorMessageSize = 512; // Sleep time of run loop, in idle case, when no time consuming operation was executed before // The lower the time the more even the CPU load is spread over time @@ -43,6 +43,7 @@ HidIoThread::HidIoThread( m_pHidDevice(pHidDevice), m_lastPollSize(0), m_pollingBufferIndex(0), + m_globalOutputReportFifo(), m_runLoopSemaphore(1) { // Initializing isn't strictly necessary but is good practice. for (int i = 0; i < kNumBuffers; i++) { @@ -188,7 +189,7 @@ QByteArray HidIoThread::getInputReport(quint8 reportID) { void HidIoThread::updateCachedOutputReportData(quint8 reportID, const QByteArray& data, - bool resendUnchangedReport) { + bool useNonSkippingFIFO) { auto mapLock = lockMutex(&m_outputReportMapMutex); if (m_outputReports.find(reportID) == m_outputReports.end()) { std::unique_ptr<HidIoOutputReport> pNewOutputReport; @@ -204,11 +205,30 @@ void HidIoThread::updateCachedOutputReportData(quint8 reportID, mapLock.unlock(); + // If useNonSkippingFIFO is false, the report data are cached here + // If useNonSkippingFIFO is true, this cache is cleared actualOutputReportIterator->second->updateCachedData( - data, m_deviceInfo, m_logOutput, resendUnchangedReport); + data, m_deviceInfo, m_logOutput, &m_globalOutputReportFifo, useNonSkippingFIFO); + + // If useNonSkippingFIFO is true, put the new report dataset on the FIFO + if (useNonSkippingFIFO) { + m_globalOutputReportFifo.addReportDatasetToFifo(reportID, data, m_deviceInfo, m_logOutput); + } } bool HidIoThread::sendNextCachedOutputReport() { + // 1.) Send non-skipping reports from FIFO + if (m_globalOutputReportFifo.sendNextReportDataset(&m_hidDeviceAndPollMutex, + m_pHidDevice, + m_deviceInfo, + m_logOutput)) { + // Return after each time consuming sendCachedData + return true; + } + + // 2.) If non non-skipping reports were in the FIFO, send the skipable reports + // from the m_outputReports cache + // m_outputReports.size() doesn't need mutex protection, because the value of i is not used. // i is just a counter to prevent infinite loop execution. // If the map size increases, this loop will execute one iteration more, diff --git a/src/controllers/hid/hidiothread.h b/src/controllers/hid/hidiothread.h index 324d95d242..96faf5b98c 100644 --- a/src/controllers/hid/hidiothread.h +++ b/src/controllers/hid/hidiothread.h @@ -7,6 +7,7 @@ #include "controllers/controller.h" #include "controllers/hid/hiddevice.h" +#include "controllers/hid/hidioglobaloutputreportfifo.h" #include "controllers/hid/hidiooutputreport.h" #include "util/compatibility/qmutex.h" #include "util/duration.h" @@ -44,7 +45,7 @@ class HidIoThread : public QThread { void updateCachedOutputReportData(quint8 reportID, const QByteArray& reportData, - bool resendUnchangedReport); + bool useNonSkippingFIFO); QByteArray getInputReport(quint8 reportID); void sendFeatureReport(quint8 reportID, const QByteArray& reportData); QByteArray getFeatureReport(quint8 reportID); @@ -92,6 +93,8 @@ class HidIoThread : public QThread { OutputReportMap m_outputReports; OutputReportMap::iterator m_outputReportIterator; + HidIoGlobalOutputReportFifo m_globalOutputReportFifo; + /// State of the HidIoThread lifecycle QAtomicInt m_state; |