diff --git a/GUI/DeviceConnectionPanel.cpp b/GUI/DeviceConnectionPanel.cpp index 6e324c48..f4a8ee0e 100644 --- a/GUI/DeviceConnectionPanel.cpp +++ b/GUI/DeviceConnectionPanel.cpp @@ -11,11 +11,13 @@ #include "limesuiteng/DeviceHandle.h" #include "limesuiteng/DeviceRegistry.h" +#include "comms/USB/GlobalHotplugEvents.h" + #include "events.h" namespace lime { -void DeviceConnectionPanel::EnumerateDevicesToChoice() +std::size_t DeviceConnectionPanel::EnumerateDevicesToChoice() { wxChoice* choice = cmbDevHandle; choice->Clear(); @@ -38,10 +40,16 @@ void DeviceConnectionPanel::EnumerateDevicesToChoice() choice->Enable(); btnDisconnect->Enable(); } + + SetSizerAndFit(szBox); + return handles.size(); } catch (std::runtime_error& e) { choice->Disable(); } + + SetSizerAndFit(szBox); + return 0; } void DeviceConnectionPanel::SetSelection(uint32_t index) @@ -55,7 +63,7 @@ void DeviceConnectionPanel::SetSelection(uint32_t index) DeviceConnectionPanel::DeviceConnectionPanel(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style) : wxPanel(parent, id, pos, size, style, "DeviceConnectionPanel") { - wxBoxSizer* szBox = new wxBoxSizer(wxHORIZONTAL); + szBox = new wxBoxSizer(wxHORIZONTAL); szBox->Add(new wxStaticText(this, wxID_ANY, _("Device:")), 0, wxALIGN_CENTER_VERTICAL, 0); cmbDevHandle = new wxChoice(this, wxNewId()); @@ -70,11 +78,14 @@ DeviceConnectionPanel::DeviceConnectionPanel(wxWindow* parent, wxWindowID id, co EnumerateDevicesToChoice(); - this->SetSizerAndFit(szBox); - // when device choice changes generate CONNECT_DEVICE cmbDevHandle->Bind(wxEVT_CHOICE, wxCommandEventHandler(DeviceConnectionPanel::SendHandleChangeEvent), this); btnDisconnect->Bind(wxEVT_BUTTON, wxCommandEventHandler(DeviceConnectionPanel::SendDisconnectEvent), this); + + Bind(limeEVT_HOTPLUG, wxCommandEventHandler(DeviceConnectionPanel::OnDeviceHotplug), this); + + GlobalHotplugEvents::AddGlobalHotplugConnectCallback(OnDeviceHotplugCallback, this); + GlobalHotplugEvents::AddGlobalHotplugDisconnectCallback(OnDeviceHotplugCallback, this); } DeviceConnectionPanel::~DeviceConnectionPanel() @@ -96,4 +107,23 @@ void DeviceConnectionPanel::SendHandleChangeEvent(wxCommandEvent& inEvent) ProcessWindowEvent(event); } +void DeviceConnectionPanel::OnDeviceHotplug(wxCommandEvent& inEvent) +{ + const auto currentSelectionString{ cmbDevHandle->GetStringSelection() }; + + const auto count = EnumerateDevicesToChoice(); + if (count > 0) + { + const auto newSelectionIndex{ cmbDevHandle->FindString(currentSelectionString) }; + cmbDevHandle->Select(newSelectionIndex); + } +} + +void DeviceConnectionPanel::OnDeviceHotplugCallback(void* data) +{ + auto* evt = new wxCommandEvent(); + evt->SetEventType(limeEVT_HOTPLUG); + wxQueueEvent(reinterpret_cast(data), evt); +} + } // namespace lime diff --git a/GUI/DeviceConnectionPanel.h b/GUI/DeviceConnectionPanel.h index 7f1fb6cb..56bb2b2f 100644 --- a/GUI/DeviceConnectionPanel.h +++ b/GUI/DeviceConnectionPanel.h @@ -3,6 +3,7 @@ #include #include +class wxBoxSizer; class wxButton; class wxChoice; @@ -23,9 +24,14 @@ class DeviceConnectionPanel : public wxPanel void SendDisconnectEvent(wxCommandEvent& inEvent); void SendHandleChangeEvent(wxCommandEvent& inEvent); - void EnumerateDevicesToChoice(); + std::size_t EnumerateDevicesToChoice(); + void OnDeviceHotplug(wxCommandEvent& inEvent); + + static void OnDeviceHotplugCallback(void* data); + wxChoice* cmbDevHandle; wxButton* btnDisconnect; + wxBoxSizer* szBox; }; } // namespace lime \ No newline at end of file diff --git a/GUI/events.cpp b/GUI/events.cpp index 089b9ef5..78090942 100644 --- a/GUI/events.cpp +++ b/GUI/events.cpp @@ -1,3 +1,4 @@ #include "events.h" wxDEFINE_EVENT(limeEVT_SDR_HANDLE_SELECTED, wxCommandEvent); +wxDEFINE_EVENT(limeEVT_HOTPLUG, wxCommandEvent); diff --git a/GUI/events.h b/GUI/events.h index bc801f48..166f9b4c 100644 --- a/GUI/events.h +++ b/GUI/events.h @@ -3,3 +3,4 @@ #include wxDECLARE_EVENT(limeEVT_SDR_HANDLE_SELECTED, wxCommandEvent); +wxDECLARE_EVENT(limeEVT_HOTPLUG, wxCommandEvent); diff --git a/GUI/fftviewer_wxgui/fftviewer_frFFTviewer.cpp b/GUI/fftviewer_wxgui/fftviewer_frFFTviewer.cpp index 826cf74b..9068938f 100644 --- a/GUI/fftviewer_wxgui/fftviewer_frFFTviewer.cpp +++ b/GUI/fftviewer_wxgui/fftviewer_frFFTviewer.cpp @@ -15,6 +15,7 @@ #include "limesuiteng/StreamConfig.h" #include "limesuiteng/complex.h" #include +#include using namespace std; using namespace lime; @@ -463,9 +464,16 @@ void fftviewer_frFFTviewer::StreamingLoop( const lime::complex32f_t* src[2] = { txPattern[0].data(), txPattern[1].data() };*/ + std::optional callbackId{ std::nullopt }; try { pthis->device->StreamSetup(config, chipIndex); + callbackId = pthis->device->AddHotplugDisconnectCallback( + [](void* data) { + auto* const viewer = reinterpret_cast(data); + viewer->StopStreaming(); + }, + pthis); pthis->device->StreamStart(chipIndex); } catch (std::logic_error& e) { @@ -646,6 +654,10 @@ void fftviewer_frFFTviewer::StreamingLoop( }*/ kiss_fft_free(m_fftCalcPlan); + if (callbackId) + { + pthis->device->RemoveHotplugDisconnectCallback(callbackId.value()); + } pthis->stopProcessing.store(true); pthis->device->StreamStop(chipIndex); diff --git a/GUI/limeGUIFrame.cpp b/GUI/limeGUIFrame.cpp index 38d34a86..2935eee0 100644 --- a/GUI/limeGUIFrame.cpp +++ b/GUI/limeGUIFrame.cpp @@ -5,9 +5,9 @@ #endif //__BORLANDC__ #include +#include #include "chips/LMS7002M/lms7002_mainPanel.h" - #include "limeGUIFrame.h" #include "dlgAbout.h" #include "lms7suiteEvents.h" @@ -19,9 +19,7 @@ #include "utility/pnlMiniLog.h" #include "FPGAcontrols_wxgui.h" #include "utility/SPI_wxgui.h" -#include #include "utility/dlgDeviceInfo.h" -#include #include "boards/pnlBoardControls.h" #include "protocols/LMSBoards.h" #include "utility/SPI_wxgui.h" @@ -34,6 +32,9 @@ #include "limesuiteng/SDRDescriptor.h" #include "DeviceTreeNode.h" #include "limesuiteng/Logger.h" +#include "DeviceConnectionPanel.h" + +#include using namespace std; using namespace lime; @@ -43,8 +44,6 @@ static constexpr int controlColumn = 1; limeGUIFrame* limeGUIFrame::obj_ptr = nullptr; -int limeGUIFrame::m_lmsSelection = 0; - void limeGUIFrame::OnGlobalLogEvent(const lime::LogLevel level, const std::string& message) { if (obj_ptr == nullptr || obj_ptr->mMiniLog == nullptr) @@ -399,6 +398,15 @@ void limeGUIFrame::OnDeviceHandleChange(wxCommandEvent& event) wxPostEvent(this, evt); UpdateConnections(lmsControl); + lmsControl->AddHotplugDisconnectCallback( + [&](void* data) { + auto* evt = new wxCommandEvent(); + evt->SetEventType(limeEVT_SDR_HANDLE_SELECTED); + evt->SetString(wxEmptyString); + wxQueueEvent(this, evt); + }, + this); + Fit(); } catch (std::runtime_error& e) { diff --git a/GUI/limeGUIFrame.h b/GUI/limeGUIFrame.h index 9e357a9c..00a53a96 100644 --- a/GUI/limeGUIFrame.h +++ b/GUI/limeGUIFrame.h @@ -29,10 +29,6 @@ #include #include -#include "IModuleFrame.h" -#include "ISOCPanel.h" -#include "DeviceConnectionPanel.h" -#include "dlgAbout.h" #include "limeGUI.h" class pnlMiniLog; @@ -40,10 +36,21 @@ class lms7002_mainPanel; class fftviewer_frFFTviewer; class LMS_Programming_wxgui; class pnlBoardControls; +class IModuleFrame; +class ISOCPanel; + +namespace lime { +class SDRDevice; +class DeviceConnectionPanel; +} // namespace lime class limeGUIFrame : public wxFrame { - protected: + public: + limeGUIFrame(wxWindow* parent, const AppArgs& appArgs); + ~limeGUIFrame(); + + private: void AddModule(IModuleFrame* module, const std::string& title); void RemoveModule(IModuleFrame* module); // Handlers for AppFrame events. @@ -57,13 +64,6 @@ class limeGUIFrame : public wxFrame void DeviceTreeSelectionChanged(wxTreeEvent& event); - public: - limeGUIFrame(wxWindow* parent, const AppArgs& appArgs); - - virtual ~limeGUIFrame(); - static int m_lmsSelection; - - protected: static void OnGlobalLogEvent(const lime::LogLevel level, const std::string& message); void OnLogMessage(wxCommandEvent& event); void UpdateConnections(lime::SDRDevice* port); diff --git a/embedded/lms7002m/privates.c b/embedded/lms7002m/privates.c index a4227998..f19b3016 100644 --- a/embedded/lms7002m/privates.c +++ b/embedded/lms7002m/privates.c @@ -59,7 +59,7 @@ void lms7002m_sleep(long timeInMicroseconds) void lms7002m_spi_write(lms7002m_context* self, uint16_t address, uint16_t value) { uint32_t mosi = address << 16 | value; - mosi |= 1 << 31; + mosi |= 0x80000000; self->hooks.spi16_transact(&mosi, NULL, 1, self->hooks.spi16_userData); } diff --git a/src/CommonFunctions.h b/src/CommonFunctions.h index 3210fc55..9fc827d4 100644 --- a/src/CommonFunctions.h +++ b/src/CommonFunctions.h @@ -1,8 +1,10 @@ #pragma once -#include -#include #include +#include +#include +#include +#include #include "limesuiteng/types.h" @@ -10,11 +12,41 @@ namespace lime { const std::string strFormat(const char* format, ...); -template std::string intToHex(T i) +template std::string intToHex(T i, bool uppercase = false) { std::stringstream stream; + + if (uppercase) + { + stream << std::uppercase; + } + stream << std::setfill('0') << std::setw(sizeof(T) * 2) << std::hex << i; return stream.str(); } +template std::vector SplitString(strView string, strView delimiter) +{ + std::vector ret; + + auto position{ string.find(delimiter) }; + + while (position != strView::npos) + { + if (position != 0) + { + ret.push_back(string.substr(0, position)); + } + + string = string.substr(position + delimiter.size()); + position = string.find(delimiter); + } + + if (string.size() > 0) + { + ret.push_back(string); + } + return ret; +} + } // namespace lime diff --git a/src/boards/DeviceFactoryFTDI.cpp b/src/boards/DeviceFactoryFTDI.cpp index 079f7b79..d5d34926 100644 --- a/src/boards/DeviceFactoryFTDI.cpp +++ b/src/boards/DeviceFactoryFTDI.cpp @@ -9,6 +9,7 @@ #include "LMS64C_FPGA_Over_USB.h" #include "CommonFunctions.h" #include "FT601/FT601.h" +#include "comms/USB/GlobalHotplugEvents.h" using namespace lime; using namespace std::literals::string_literals; @@ -25,6 +26,7 @@ static const std::set ids{ { 0x0403, 0x601F } }; DeviceFactoryFTDI::DeviceFactoryFTDI() : DeviceRegistryEntry("FTDI"s) { + GlobalHotplugEvents::AddVidPids(ids); } std::vector DeviceFactoryFTDI::enumerate(const DeviceHandle& hint) diff --git a/src/boards/DeviceFactoryFX3.cpp b/src/boards/DeviceFactoryFX3.cpp index e6814f56..fa3c9a53 100644 --- a/src/boards/DeviceFactoryFX3.cpp +++ b/src/boards/DeviceFactoryFX3.cpp @@ -1,6 +1,8 @@ #include "DeviceFactoryFX3.h" #include +#include +#include #include #include "limesuiteng/DeviceHandle.h" @@ -10,6 +12,7 @@ #include "LMS64C_LMS7002M_Over_USB.h" #include "LMS64C_FPGA_Over_USB.h" #include "CommonFunctions.h" +#include "comms/USB/GlobalHotplugEvents.h" using namespace lime; using namespace std::literals::string_literals; @@ -26,6 +29,7 @@ static const std::set ids{ { 0x04B4, 0x00F1 }, { 0x04B4, DeviceFactoryFX3::DeviceFactoryFX3() : DeviceRegistryEntry("FX3"s) { + GlobalHotplugEvents::AddVidPids(ids); } std::vector DeviceFactoryFX3::enumerate(const DeviceHandle& hint) diff --git a/src/boards/LMS7002M_SDRDevice.cpp b/src/boards/LMS7002M_SDRDevice.cpp index e30790f4..554d0d54 100644 --- a/src/boards/LMS7002M_SDRDevice.cpp +++ b/src/boards/LMS7002M_SDRDevice.cpp @@ -1274,4 +1274,14 @@ OpStatus LMS7002M_SDRDevice::LMS7002TestSignalConfigure(LMS7002M* chip, const Ch return OpStatus::Success; } +std::size_t LMS7002M_SDRDevice::AddHotplugDisconnectCallback(const HotplugDisconnectCallbackType& function, void* userData) +{ + return 0; +} + +void LMS7002M_SDRDevice::RemoveHotplugDisconnectCallback(std::size_t id) +{ + return; +} + } // namespace lime diff --git a/src/boards/LMS7002M_SDRDevice.h b/src/boards/LMS7002M_SDRDevice.h index 477b0bd8..d756975c 100644 --- a/src/boards/LMS7002M_SDRDevice.h +++ b/src/boards/LMS7002M_SDRDevice.h @@ -125,6 +125,9 @@ class LIME_API LMS7002M_SDRDevice : public SDRDevice /// @copydoc FPGA::WriteRegister() virtual OpStatus WriteFPGARegister(uint32_t address, uint32_t value); + std::size_t AddHotplugDisconnectCallback(const HotplugDisconnectCallbackType& function, void* userData) override; + void RemoveHotplugDisconnectCallback(std::size_t id) override; + protected: static OpStatus UpdateFPGAInterfaceFrequency(LMS7002M& soc, FPGA& fpga, uint8_t chipIndex); void SetGainInformationInDescriptor(RFSOCDescriptor& descriptor); diff --git a/src/boards/LimeSDR/LimeSDR.cpp b/src/boards/LimeSDR/LimeSDR.cpp index 149b692f..89e02530 100644 --- a/src/boards/LimeSDR/LimeSDR.cpp +++ b/src/boards/LimeSDR/LimeSDR.cpp @@ -24,11 +24,12 @@ #include #include -using namespace lime; using namespace lime::LMS64CProtocol; using namespace lime::LMS7002MCSR_Data; using namespace std::literals::string_literals; +namespace lime { + static const uint8_t SPI_LMS7002M = 0; static const uint8_t SPI_FPGA = 1; static const uint8_t SPI_ADF4002 = 2; @@ -154,6 +155,18 @@ LimeSDR::LimeSDR(std::shared_ptr spiLMS, mDeviceDescriptor = descriptor; + mStreamPort->AddOnHotplugDisconnectCallback( + [](void* userData) { + auto* const sdr = reinterpret_cast(userData); + + // Call in reverse order so that the "smaller" scoped callbacks run first. + for (auto iter = sdr->disconnectCallbacks.rbegin(); iter != sdr->disconnectCallbacks.rend(); ++iter) + { + (*iter)(); + } + }, + this); + //must configure synthesizer before using LimeSDR /*if (info.device == LMS_DEV_LIMESDR && info.hardware < 4) { @@ -601,3 +614,28 @@ OpStatus LimeSDR::MemoryRead(std::shared_ptr storage, Region region return OpStatus::Error; return mfpgaPort->MemoryRead(region.address, data, region.size); } + +std::size_t LimeSDR::AddHotplugDisconnectCallback(const HotplugDisconnectCallbackType& function, void* userData) +{ + std::size_t id = 0; + + if (!disconnectCallbacks.empty()) + { + // As long as elements are not out of order this guarantees a unique ID in the array. + id = disconnectCallbacks.back().id + 1; + } + + disconnectCallbacks.push_back({ function, userData, id }); + + return id; +} + +void LimeSDR::RemoveHotplugDisconnectCallback(std::size_t id) +{ + disconnectCallbacks.erase(std::remove_if(disconnectCallbacks.begin(), + disconnectCallbacks.end(), + [&id](const CallbackInfo& info) { return id == info.id; }), + disconnectCallbacks.end()); +} + +} // namespace lime diff --git a/src/boards/LimeSDR/LimeSDR.h b/src/boards/LimeSDR/LimeSDR.h index 14520e57..5e065185 100644 --- a/src/boards/LimeSDR/LimeSDR.h +++ b/src/boards/LimeSDR/LimeSDR.h @@ -56,6 +56,9 @@ class LimeSDR : public LMS7002M_SDRDevice OpStatus MemoryWrite(std::shared_ptr storage, Region region, const void* data) override; OpStatus MemoryRead(std::shared_ptr storage, Region region, void* data) override; + std::size_t AddHotplugDisconnectCallback(const HotplugDisconnectCallbackType& function, void* userData) override; + void RemoveHotplugDisconnectCallback(std::size_t id) override; + protected: SDRDescriptor GetDeviceInfo(); void ResetUSBFIFO(); @@ -77,6 +80,9 @@ class LimeSDR : public LMS7002M_SDRDevice std::shared_ptr mlms7002mPort; std::shared_ptr mfpgaPort; bool mConfigInProgress; + + std::vector> disconnectCallbacks; + std::size_t mStreamStopCallbackId; }; } // namespace lime diff --git a/src/boards/LimeSDR_Mini/LimeSDR_Mini.cpp b/src/boards/LimeSDR_Mini/LimeSDR_Mini.cpp index fe54303b..7e6364a9 100644 --- a/src/boards/LimeSDR_Mini/LimeSDR_Mini.cpp +++ b/src/boards/LimeSDR_Mini/LimeSDR_Mini.cpp @@ -23,12 +23,13 @@ #include #include -using namespace lime; using namespace lime::LMS64CProtocol; using namespace lime::EqualizerCSR; using namespace lime::LMS7002MCSR_Data; using namespace std::literals::string_literals; +namespace lime { + static const int STREAM_BULK_WRITE_ADDRESS = 0x03; static const int STREAM_BULK_READ_ADDRESS = 0x83; @@ -205,6 +206,18 @@ LimeSDR_Mini::LimeSDR_Mini(std::shared_ptr spiLMS, descriptor.socTree->children.push_back(fpgaNode); mDeviceDescriptor = descriptor; + + mStreamPort->AddOnHotplugDisconnectCallback( + [](void* userData) { + auto* const mini = reinterpret_cast(userData); + + // Call in reverse order so that the "smaller" scoped callbacks run first. + for (auto iter = mini->disconnectCallbacks.rbegin(); iter != mini->disconnectCallbacks.rend(); ++iter) + { + (*iter)(); + } + }, + this); } LimeSDR_Mini::~LimeSDR_Mini() @@ -660,3 +673,28 @@ void LimeSDR_Mini::SetSerialNumber(const std::string& number) sscanf(number.c_str(), "%16lX", &sn); mDeviceDescriptor.serialNumber = sn; } + +std::size_t LimeSDR_Mini::AddHotplugDisconnectCallback(const HotplugDisconnectCallbackType& function, void* userData) +{ + std::size_t id = 0; + + if (!disconnectCallbacks.empty()) + { + // As long as elements are not out of order this guarantees a unique ID in the array. + id = disconnectCallbacks.back().id + 1; + } + + disconnectCallbacks.push_back({ function, userData, id }); + + return id; +} + +void LimeSDR_Mini::RemoveHotplugDisconnectCallback(std::size_t id) +{ + disconnectCallbacks.erase(std::remove_if(disconnectCallbacks.begin(), + disconnectCallbacks.end(), + [&id](const CallbackInfo& info) { return id == info.id; }), + disconnectCallbacks.end()); +} + +} // namespace lime diff --git a/src/boards/LimeSDR_Mini/LimeSDR_Mini.h b/src/boards/LimeSDR_Mini/LimeSDR_Mini.h index 32bb480b..b69dd85b 100644 --- a/src/boards/LimeSDR_Mini/LimeSDR_Mini.h +++ b/src/boards/LimeSDR_Mini/LimeSDR_Mini.h @@ -9,7 +9,6 @@ namespace lime { -class USBGeneric; class IComms; class IUSB; @@ -54,6 +53,9 @@ class LimeSDR_Mini : public LMS7002M_SDRDevice void SetSerialNumber(const std::string& number); + std::size_t AddHotplugDisconnectCallback(const HotplugDisconnectCallbackType& function, void* userData) override; + void RemoveHotplugDisconnectCallback(std::size_t id) override; + protected: SDRDescriptor GetDeviceInfo(); static OpStatus UpdateFPGAInterface(void* userData); @@ -64,6 +66,8 @@ class LimeSDR_Mini : public LMS7002M_SDRDevice std::shared_ptr mlms7002mPort; std::shared_ptr mfpgaPort; bool mConfigInProgress{}; + + std::vector> disconnectCallbacks; }; } // namespace lime diff --git a/src/boards/MMX8/MM_X8.cpp b/src/boards/MMX8/MM_X8.cpp index 3a00182a..d073571f 100644 --- a/src/boards/MMX8/MM_X8.cpp +++ b/src/boards/MMX8/MM_X8.cpp @@ -830,4 +830,14 @@ OpStatus LimeSDR_MMX8::UploadTxWaveform(const StreamConfig& config, uint8_t modu return mSubDevices[moduleIndex]->UploadTxWaveform(config, 0, samples, count); } +std::size_t LimeSDR_MMX8::AddHotplugDisconnectCallback(const HotplugDisconnectCallbackType& function, void* userData) +{ + return 0; +} + +void LimeSDR_MMX8::RemoveHotplugDisconnectCallback(std::size_t id) +{ + return; +} + } //namespace lime diff --git a/src/boards/MMX8/MM_X8.h b/src/boards/MMX8/MM_X8.h index 032f870e..a7f990ca 100644 --- a/src/boards/MMX8/MM_X8.h +++ b/src/boards/MMX8/MM_X8.h @@ -149,6 +149,9 @@ class LimeSDR_MMX8 : public SDRDevice OpStatus MemoryRead(std::shared_ptr storage, Region region, void* data) override; OpStatus UploadTxWaveform(const StreamConfig& config, uint8_t moduleIndex, const void** samples, uint32_t count) override; + std::size_t AddHotplugDisconnectCallback(const HotplugDisconnectCallbackType& function, void* userData) override; + void RemoveHotplugDisconnectCallback(std::size_t id) override; + private: std::shared_ptr mMainFPGAcomms; SDRDescriptor mDeviceDescriptor; diff --git a/src/cli/limeTRX.cpp b/src/cli/limeTRX.cpp index 980b71df..d8e743c3 100644 --- a/src/cli/limeTRX.cpp +++ b/src/cli/limeTRX.cpp @@ -467,6 +467,8 @@ int main(int argc, char** argv) else device->StreamStart(chipIndex); + device->AddHotplugDisconnectCallback([](void* data) { *reinterpret_cast(data) = true; }, &stopProgram); + auto startTime = std::chrono::high_resolution_clock::now(); auto t1 = startTime - std::chrono::seconds(2); // rewind t1 to do update on first loop auto t2 = t1; diff --git a/src/comms/USB/CMakeLists.txt b/src/comms/USB/CMakeLists.txt index dfac03cb..12b8b546 100644 --- a/src/comms/USB/CMakeLists.txt +++ b/src/comms/USB/CMakeLists.txt @@ -11,6 +11,7 @@ if(NOT ENABLE_USB_FX3 AND NOT ENABLE_USB_FTDI) endif() set(COMMS_USB_SOURCES + ${THIS_SOURCE_DIR}/GlobalHotplugEvents.cpp ${THIS_SOURCE_DIR}/LMS64C_FPGA_Over_USB.cpp ${THIS_SOURCE_DIR}/LMS64C_LMS7002M_Over_USB.cpp ${THIS_SOURCE_DIR}/LMS64C_ADF4002_Over_USB.cpp @@ -19,9 +20,12 @@ set(COMMS_USB_SOURCES ######################################################################## ## Add to library ######################################################################## -target_include_directories(${MAIN_LIBRARY_NAME} PRIVATE ${THIS_SOURCE_DIR}) target_sources(${MAIN_LIBRARY_NAME} PRIVATE ${COMMS_USB_SOURCES}) if(UNIX) target_sources(${MAIN_LIBRARY_NAME} PRIVATE ${THIS_SOURCE_DIR}/USBGeneric.cpp) +else() + target_sources( + ${MAIN_LIBRARY_NAME} PRIVATE ${THIS_SOURCE_DIR}/WindowsDeviceHotplug.cpp ${THIS_SOURCE_DIR}/WindowsGlobalHotplug.cpp) + target_link_options(${MAIN_LIBRARY_NAME} PUBLIC "cfgmgr32.lib") endif() diff --git a/src/comms/USB/FT601/FT601.cpp b/src/comms/USB/FT601/FT601.cpp index 8f68df78..cb08c026 100644 --- a/src/comms/USB/FT601/FT601.cpp +++ b/src/comms/USB/FT601/FT601.cpp @@ -71,7 +71,7 @@ FT601::~FT601() Disconnect(); } -bool FT601::Connect(uint16_t vid, uint16_t pid, const char* serial) +bool FT601::Connect(uint16_t vid, uint16_t pid, const std::string& serial) { Disconnect(); #ifdef __unix__ @@ -95,7 +95,7 @@ bool FT601::Connect(uint16_t vid, uint16_t pid, const char* serial) FT_STATUS ftStatus = FT_OK; DWORD dwNumDevices = 0; // Open a device - ftStatus = FT_Create(reinterpret_cast(const_cast(serial)), FT_OPEN_BY_SERIAL_NUMBER, &mFTHandle); + ftStatus = FT_Create(reinterpret_cast(const_cast(serial.c_str())), FT_OPEN_BY_SERIAL_NUMBER, &mFTHandle); if (FT_FAILED(ftStatus)) return false; @@ -110,6 +110,14 @@ bool FT601::Connect(uint16_t vid, uint16_t pid, const char* serial) FT_SetPipeTimeout(mFTHandle, CONTROL_BULK_READ_ADDRESS, 500); FT_SetPipeTimeout(mFTHandle, STREAM_BULK_READ_ADDRESS, 100); FT_SetPipeTimeout(mFTHandle, STREAM_BULK_WRITE_ADDRESS, 100); + + hotplug.AddDeviceToReceiveHotplugDisconnectEvents(vid, pid, serial); + hotplug.AddOnHotplugDisconnectCallback( + [](void* data) { + auto* ft601 = reinterpret_cast(data); + ft601->Disconnect(); + }, + this); return true; #endif } @@ -373,4 +381,13 @@ int FT601::FT_SetStreamPipe(unsigned char ep, size_t size) } #endif +void FT601::AddOnHotplugDisconnectCallback(const IUSB::HotplugDisconnectCallbackType& function, void* userData) +{ +#ifdef __unix__ + libusb_impl.AddOnHotplugDisconnectCallback(function, userData); +#else + hotplug.AddOnHotplugDisconnectCallback(function, userData); +#endif +} + } // namespace lime diff --git a/src/comms/USB/FT601/FT601.h b/src/comms/USB/FT601/FT601.h index 50ab8657..03ed631d 100644 --- a/src/comms/USB/FT601/FT601.h +++ b/src/comms/USB/FT601/FT601.h @@ -6,6 +6,7 @@ #include "comms/USB/USBGeneric.h" #else #include "FTD3XXLibrary/FTD3XX.h" + #include "comms/USB/WindowsDeviceHotplug.h" #endif namespace lime { @@ -19,7 +20,7 @@ class FT601 : public IUSB FT601(); virtual ~FT601(); - bool Connect(uint16_t vid, uint16_t pid, const char* serial) override; + bool Connect(uint16_t vid, uint16_t pid, const std::string& serial) override; void Disconnect() override; bool IsConnected() override; @@ -41,6 +42,8 @@ class FT601 : public IUSB */ OpStatus ResetStreamBuffers(); + void AddOnHotplugDisconnectCallback(const HotplugDisconnectCallbackType& function, void* userData) override; + protected: #ifdef __unix__ USBGeneric libusb_impl; @@ -50,6 +53,8 @@ class FT601 : public IUSB #else FT_HANDLE mFTHandle; int ReinitPipe(unsigned char ep); + + WindowsDeviceHotplug hotplug{}; #endif }; diff --git a/src/comms/USB/FX3/FX3.cpp b/src/comms/USB/FX3/FX3.cpp index 10b08f81..e419f0a1 100644 --- a/src/comms/USB/FX3/FX3.cpp +++ b/src/comms/USB/FX3/FX3.cpp @@ -2,7 +2,6 @@ #include -using namespace lime; using namespace std::literals::string_literals; #ifdef __unix__ @@ -14,13 +13,18 @@ using namespace std::literals::string_literals; #ifdef __GNUC__ #pragma GCC diagnostic pop #endif -const int FX3::CTR_WRITE_REQUEST_VALUE = LIBUSB_REQUEST_TYPE_VENDOR; -const int FX3::CTR_READ_REQUEST_VALUE = LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN; #else - #include "windows.h" + #include "Windows.h" #include "CyAPI.h" #include +#endif + +namespace lime { +#ifdef __unix__ +const int FX3::CTR_WRITE_REQUEST_VALUE = LIBUSB_REQUEST_TYPE_VENDOR; +const int FX3::CTR_READ_REQUEST_VALUE = LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN; +#else class FX3AsyncContext { public: @@ -98,7 +102,7 @@ FX3::~FX3() Disconnect(); } -bool FX3::Connect(uint16_t vid, uint16_t pid, const char* serial) +bool FX3::Connect(uint16_t vid, uint16_t pid, const std::string& serial) { Disconnect(); #ifdef __unix__ @@ -107,8 +111,25 @@ bool FX3::Connect(uint16_t vid, uint16_t pid, const char* serial) if (fx3device->DeviceCount() == 0) return false; - if (fx3device->Open(0) == false) + for (UCHAR i = 0; i < fx3device->DeviceCount(); ++i) + { + if (fx3device->Open(i) == false) + continue; + + if (fx3device->VendorID == vid && fx3device->ProductID == pid && + (serial.empty() || + std::string{ std::wstring_convert>().to_bytes(fx3device->SerialNumber) } == serial)) + { + break; + } + + fx3device->Close(); + } + + if (!fx3device->IsOpen()) + { return false; + } for (int i = 0; i < fx3device->EndPointCount(); ++i) { @@ -117,6 +138,14 @@ bool FX3::Connect(uint16_t vid, uint16_t pid, const char* serial) ep->SetXferSize(len); endpoints[ep->Address] = ep; } + + hotplug.AddDeviceToReceiveHotplugDisconnectEvents(vid, pid, serial); + hotplug.AddOnHotplugDisconnectCallback( + [](void* data) { + auto* fx3 = reinterpret_cast(data); + fx3->Disconnect(); + }, + this); #endif return true; } @@ -250,3 +279,14 @@ void FX3::FreeAsyncContext(void* context) delete reinterpret_cast(context); #endif } + +void FX3::AddOnHotplugDisconnectCallback(const IUSB::HotplugDisconnectCallbackType& function, void* userData) +{ +#ifdef __unix__ + libusb_impl.AddOnHotplugDisconnectCallback(function, userData); +#else + hotplug.AddOnHotplugDisconnectCallback(function, userData); +#endif +} + +} // namespace lime diff --git a/src/comms/USB/FX3/FX3.h b/src/comms/USB/FX3/FX3.h index bc4197b7..8b8acb31 100644 --- a/src/comms/USB/FX3/FX3.h +++ b/src/comms/USB/FX3/FX3.h @@ -7,11 +7,14 @@ #ifdef __unix__ #include "comms/USB/USBGeneric.h" -#endif // !__unix__ +#else + #include "comms/USB/WindowsDeviceHotplug.h" class CCyFX3Device; class CCyUSBEndPoint; +#endif // !__unix__ + namespace lime { /// @brief A class for communicating with devices using the Cypress USB 3.0 CYUSB3014-BZXC USB controller. @@ -23,7 +26,7 @@ class FX3 : public IUSB FX3(); virtual ~FX3(); - bool Connect(uint16_t vid, uint16_t pid, const char* serial) override; + bool Connect(uint16_t vid, uint16_t pid, const std::string& serial) override; void Disconnect() override; bool IsConnected() override; @@ -38,13 +41,16 @@ class FX3 : public IUSB int32_t ControlTransfer( int requestType, int request, int value, int index, uint8_t* data, size_t length, int32_t timeout_ms) override; + void AddOnHotplugDisconnectCallback(const HotplugDisconnectCallbackType& function, void* userData) override; + static const int CTR_WRITE_REQUEST_VALUE; static const int CTR_READ_REQUEST_VALUE; - static constexpr uint8_t CONTROL_BULK_OUT_ADDRESS = - 0x0F; ///< The memory address for writing information via the bulk transfer protocol. - static constexpr uint8_t CONTROL_BULK_IN_ADDRESS = - 0x8F; ///< THe memory address for reading information via the bulk transfer protocol. + /// The memory address for writing information via the bulk transfer protocol. + static constexpr uint8_t CONTROL_BULK_OUT_ADDRESS = 0x0F; + /// The memory address for reading information via the bulk transfer protocol. + static constexpr uint8_t CONTROL_BULK_IN_ADDRESS = 0x8F; + protected: #ifdef __unix__ USBGeneric libusb_impl; @@ -54,6 +60,8 @@ class FX3 : public IUSB //end points for samples reading and writing std::map endpoints{}; + + WindowsDeviceHotplug hotplug{}; #endif }; diff --git a/src/comms/USB/GlobalHotplugEvents.cpp b/src/comms/USB/GlobalHotplugEvents.cpp new file mode 100644 index 00000000..e3ef07d8 --- /dev/null +++ b/src/comms/USB/GlobalHotplugEvents.cpp @@ -0,0 +1,89 @@ +#include "GlobalHotplugEvents.h" + +#include "comms/USB/IUSB.h" + +#include +#include + +namespace lime { + +std::set GlobalHotplugEvents::vidPidPairsToLookFor{}; + +std::vector> + GlobalHotplugEvents::globalHotplugConnectCallbacks{}; +std::vector> + GlobalHotplugEvents::globalHotplugDisconnectCallbacks{}; + +#ifdef __unix__ +USBGeneric GlobalHotplugEvents::hotplug{}; +#else +WindowsGlobalHotplug GlobalHotplugEvents::hotplug{}; +#endif + +void GlobalHotplugEvents::AddVidPids(const std::set& ids) +{ + vidPidPairsToLookFor.insert(ids.begin(), ids.end()); +} + +std::set GlobalHotplugEvents::GetVidPids() +{ + return vidPidPairsToLookFor; +} + +std::vector> +GlobalHotplugEvents::GetConnectCallbacks() +{ + return globalHotplugConnectCallbacks; +} + +std::vector> +GlobalHotplugEvents::GetDisconnectCallbacks() +{ + return globalHotplugDisconnectCallbacks; +} + +std::size_t GlobalHotplugEvents::AddGlobalHotplugConnectCallback(const GlobalHotplugConnectCallbackType& function, void* userData) +{ + return AddCallback(globalHotplugConnectCallbacks, function, userData); +} + +void GlobalHotplugEvents::RemoveGlobalHotplugConnectCallback(std::size_t id) +{ + RemoveCallback(globalHotplugConnectCallbacks, id); +} + +std::size_t GlobalHotplugEvents::AddGlobalHotplugDisconnectCallback( + const GlobalHotplugDisconnectCallbackType& function, void* userData) +{ + return AddCallback(globalHotplugDisconnectCallbacks, function, userData); +} + +void GlobalHotplugEvents::RemoveGlobalHotplugDisconnectCallback(std::size_t id) +{ + RemoveCallback(globalHotplugDisconnectCallbacks, id); +} + +template +std::size_t GlobalHotplugEvents::AddCallback(std::vector>& vector, const T& function, void* userData) +{ + std::size_t id = 0; + + if (!vector.empty()) + { + // As long as elements are not out of order this guarantees a unique ID in the array. + id = vector.back().id + 1; + } + + vector.push_back({ function, userData, id }); + + return id; +} + +template void GlobalHotplugEvents::RemoveCallback(std::vector>& vector, std::size_t id) +{ + vector.erase( + std::remove_if(vector.begin(), vector.end(), [&id](const SDRDevice::CallbackInfo& info) { return id == info.id; }), + vector.end()); +} + +} // namespace lime diff --git a/src/comms/USB/GlobalHotplugEvents.h b/src/comms/USB/GlobalHotplugEvents.h new file mode 100644 index 00000000..451ff182 --- /dev/null +++ b/src/comms/USB/GlobalHotplugEvents.h @@ -0,0 +1,68 @@ +#ifndef LIME_GLOBALHOTPLUGEVENTS_H +#define LIME_GLOBALHOTPLUGEVENTS_H + +#include "comms/USB/IUSB.h" +#include "limesuiteng/config.h" +#include "limesuiteng/SDRDevice.h" + +#ifdef __unix__ + #include "comms/USB/USBGeneric.h" +#else + #include "comms/USB/WindowsGlobalHotplug.h" +#endif + +namespace lime { + +class GlobalHotplugEvents +{ + public: + static void AddVidPids(const std::set& ids); + static std::set GetVidPids(); + + typedef std::function GlobalHotplugConnectCallbackType; + typedef std::function GlobalHotplugDisconnectCallbackType; + + static std::vector> GetConnectCallbacks(); + static std::vector> GetDisconnectCallbacks(); + + /// @brief Adds a callback to run when a global hotplug connect event happens (currently USB only). + /// @param function The function to run when a connect happens. + /// @param userData The data to pass into the function when it runs. + /// @return The ID of the callback for removal later. + static LIME_API std::size_t AddGlobalHotplugConnectCallback(const GlobalHotplugConnectCallbackType& function, void* userData); + + /// @brief Removes the given global hotplug connect callback by ID. + /// @param id The ID of the callback to remove. + static LIME_API void RemoveGlobalHotplugConnectCallback(std::size_t id); + + /// @brief Adds a callback to run when a global hotplug disconnect event happens (currently USB only). + /// @param function The function to run when a disconnect happens. + /// @param userData The data to pass into the function when it runs. + /// @return The ID of the callback for removal later. + static LIME_API std::size_t AddGlobalHotplugDisconnectCallback( + const GlobalHotplugDisconnectCallbackType& function, void* userData); + + /// @brief Removes the given global hotplug disconnect callback by ID. + /// @param id The ID of the callback to remove. + static LIME_API void RemoveGlobalHotplugDisconnectCallback(std::size_t id); + + private: + static std::set vidPidPairsToLookFor; + + static std::vector> globalHotplugConnectCallbacks; + static std::vector> globalHotplugDisconnectCallbacks; + +#ifdef __unix__ + static USBGeneric hotplug; +#else + static WindowsGlobalHotplug hotplug; +#endif + + template + static std::size_t AddCallback(std::vector>& vector, const T& function, void* userData); + template static void RemoveCallback(std::vector>& vector, std::size_t id); +}; + +} // namespace lime + +#endif // LIME_GLOBALHOTPLUGEVENTS_H diff --git a/src/comms/USB/IUSB.h b/src/comms/USB/IUSB.h index b7dbf15f..972e3505 100644 --- a/src/comms/USB/IUSB.h +++ b/src/comms/USB/IUSB.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "limesuiteng/OpStatus.h" namespace lime { @@ -22,8 +23,16 @@ class IUSB struct VendorProductId { uint16_t vendorId; uint16_t productId; - bool operator<(const VendorProductId& rhs) const { return vendorId < rhs.vendorId || productId < rhs.productId; } + bool operator<(const VendorProductId& rhs) const + { + if (vendorId == rhs.vendorId) + { + return productId < rhs.productId; + } + return vendorId < rhs.vendorId; + } }; + /** * @brief Returns list of detected devices descriptors used for connecting to device. * @param ids Set of vendor and product IDs to search for @@ -39,7 +48,7 @@ class IUSB @param serial The serial number of the device. @return The status of the operation (true on success). */ - virtual bool Connect(uint16_t vid, uint16_t pid, const char* serial = nullptr) = 0; + virtual bool Connect(uint16_t vid, uint16_t pid, const std::string& serial = "") = 0; /** @brief Returns whether this instance is connected to a device. @@ -116,6 +125,15 @@ class IUSB @param context Pointer to transfer context (retuned by AllocateAsyncContext). */ virtual void FreeAsyncContext(void* context) = 0; + + typedef std::function HotplugDisconnectCallbackType; + template struct CallbackInfo { + T function; + void* userData; + + void operator()() { return function(userData); } + }; + virtual void AddOnHotplugDisconnectCallback(const HotplugDisconnectCallbackType& function, void* userData) = 0; }; } // namespace lime diff --git a/src/comms/USB/USBGeneric.cpp b/src/comms/USB/USBGeneric.cpp index bf44510a..772a46ce 100644 --- a/src/comms/USB/USBGeneric.cpp +++ b/src/comms/USB/USBGeneric.cpp @@ -15,6 +15,7 @@ #endif #include "limesuiteng/Logger.h" +#include "GlobalHotplugEvents.h" using namespace std::literals::string_literals; @@ -24,6 +25,8 @@ static libusb_context* gContextLibUsb{ nullptr }; static int activeUSBconnections = 0; static std::thread gUSBProcessingThread; // single thread for processing USB callbacks +static libusb_hotplug_callback_handle globalCallbackHandle; + static void HandleLibusbEvents(libusb_context* context) { timeval tv{}; @@ -38,6 +41,46 @@ static void HandleLibusbEvents(libusb_context* context) } } +static int GlobalHotplugCallback(libusb_context* ctx, libusb_device* device, libusb_hotplug_event event, void* user_data) +{ + libusb_device_descriptor desc{}; + const auto returnCode = libusb_get_device_descriptor(device, &desc); + if (returnCode < 0) + { + lime::error("Failed to get device description"s); + return 0; + } + + const IUSB::VendorProductId vidPid{ desc.idVendor, desc.idProduct }; + const auto& matchTo = GlobalHotplugEvents::GetVidPids(); + + // Figure out if it's a device we care about + if (matchTo.find(vidPid) == matchTo.end()) + { + // It's not something we care about, this is the only value we can return here though + return 0; + } + + if (LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED == event) + { + const auto& callbacks = GlobalHotplugEvents::GetConnectCallbacks(); + for (auto iter = callbacks.rbegin(); iter != callbacks.rend(); ++iter) + { + (*iter)(); + } + } + else if (LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT == event) + { + const auto& callbacks = GlobalHotplugEvents::GetDisconnectCallbacks(); + for (auto iter = callbacks.rbegin(); iter != callbacks.rend(); ++iter) + { + (*iter)(); + } + } + + return 0; +} + static int SessionRefCountIncrement() { ++activeUSBconnections; @@ -55,7 +98,19 @@ static int SessionRefCountIncrement() LIBUSB_LOG_LEVEL_INFO); // Set verbosity level to info, as suggested in the documentation #endif if (gContextLibUsb) + { gUSBProcessingThread = std::thread(HandleLibusbEvents, gContextLibUsb); + + libusb_hotplug_register_callback(gContextLibUsb, + LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, + 0, + LIBUSB_HOTPLUG_MATCH_ANY, + LIBUSB_HOTPLUG_MATCH_ANY, + LIBUSB_HOTPLUG_MATCH_ANY, + GlobalHotplugCallback, + nullptr, + &globalCallbackHandle); + } } return activeUSBconnections; } @@ -65,6 +120,7 @@ static int SessionRefCountDecrement() --activeUSBconnections; if (activeUSBconnections == 0 && gUSBProcessingThread.joinable()) { + libusb_hotplug_deregister_callback(gContextLibUsb, globalCallbackHandle); gUSBProcessingThread.join(); libusb_exit(gContextLibUsb); gContextLibUsb = nullptr; @@ -110,6 +166,20 @@ static void process_libusbtransfer(libusb_transfer* trans) context->cv.notify_one(); } +// Runs within the gUSBProcessingThread thread +int USBGeneric::DeviceHotplugCallback(libusb_context* ctx, libusb_device* device, libusb_hotplug_event event, void* user_data) +{ + auto* usb = reinterpret_cast(user_data); + + for (auto iter = usb->hotplugDisconnectCallbacks.rbegin(); iter != usb->hotplugDisconnectCallbacks.rend(); ++iter) + { + (*iter)(); + } + + usb->Disconnect(); + return 1; +} + USBGeneric::AsyncContext::AsyncContext() : transfer(libusb_alloc_transfer(0)) , bytesXfered(0) @@ -205,7 +275,7 @@ USBGeneric::~USBGeneric() SessionRefCountDecrement(); } -bool USBGeneric::Connect(uint16_t vid, uint16_t pid, const char* serial) +bool USBGeneric::Connect(uint16_t vid, uint16_t pid, const std::string& serial) { libusb_device** devs; // Pointer to pointer of device, used to retrieve a list of devices int usbDeviceCount = libusb_get_device_list(gContextLibUsb, &devs); @@ -257,8 +327,20 @@ bool USBGeneric::Connect(uint16_t vid, uint16_t pid, const char* serial) foundSerial = std::string(data, static_cast(stringLength)); } - if (std::string{ serial }.empty() || std::string{ serial } == foundSerial) + if (serial.empty() || serial == foundSerial) + { + libusb_hotplug_register_callback(gContextLibUsb, + LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, + 0, + desc.idVendor, + desc.idProduct, + LIBUSB_HOTPLUG_MATCH_ANY, + DeviceHotplugCallback, + this, + nullptr); + break; //found it + } libusb_close(dev_handle); dev_handle = nullptr; @@ -340,6 +422,11 @@ OpStatus USBGeneric::BeginDataXfer(void* context, uint8_t* buffer, size_t length libusb_fill_bulk_transfer(tr, dev_handle, endPointAddr, buffer, length, process_libusbtransfer, xfer, 0); xfer->done.store(false); xfer->bytesXfered = 0; + if (tr->dev_handle == nullptr) + { + return OpStatus::Error; + } + int status = libusb_submit_transfer(tr); if (status != 0) { @@ -409,4 +496,9 @@ OpStatus USBGeneric::ClaimInterface(int32_t interface_number) return OpStatus::Success; } +void USBGeneric::AddOnHotplugDisconnectCallback(const IUSB::HotplugDisconnectCallbackType& function, void* userData) +{ + hotplugDisconnectCallbacks.push_back({ function, userData }); +} + } // namespace lime diff --git a/src/comms/USB/USBGeneric.h b/src/comms/USB/USBGeneric.h index 90a1f988..03e89e43 100644 --- a/src/comms/USB/USBGeneric.h +++ b/src/comms/USB/USBGeneric.h @@ -5,8 +5,16 @@ #include #include -struct libusb_device_handle; -struct libusb_transfer; +#ifdef __unix__ + #ifdef __GNUC__ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wpedantic" + #endif + #include + #ifdef __GNUC__ + #pragma GCC diagnostic pop + #endif +#endif namespace lime { @@ -35,7 +43,7 @@ class USBGeneric : IUSB virtual ~USBGeneric(); static constexpr int32_t defaultTimeout = 1000; ///< The default timeout to use if none is specified. - bool Connect(uint16_t vid, uint16_t pid, const char* serial) override; + bool Connect(uint16_t vid, uint16_t pid, const std::string& serial) override; bool IsConnected() override; void Disconnect() override; @@ -52,10 +60,16 @@ class USBGeneric : IUSB OpStatus AbortXfer(void* context) override; void FreeAsyncContext(void* context) override; - virtual OpStatus ClaimInterface(int32_t interface_number); + OpStatus ClaimInterface(int32_t interface_number); - protected: + void AddOnHotplugDisconnectCallback(const HotplugDisconnectCallbackType& function, void* userData) override; + + private: libusb_device_handle* dev_handle; //a device handle + + std::vector> hotplugDisconnectCallbacks{}; + + static int DeviceHotplugCallback(libusb_context* ctx, libusb_device* device, libusb_hotplug_event event, void* user_data); }; } // namespace lime diff --git a/src/comms/USB/WindowsDeviceHotplug.cpp b/src/comms/USB/WindowsDeviceHotplug.cpp new file mode 100644 index 00000000..65502311 --- /dev/null +++ b/src/comms/USB/WindowsDeviceHotplug.cpp @@ -0,0 +1,116 @@ +#include "WindowsDeviceHotplug.h" + +#include +#include + +#include "CommonFunctions.h" +#include "IUSB.h" + +#include +#include +#include +#include +#include +#include + +using namespace std::literals::string_literals; +using namespace std::literals::string_view_literals; + +namespace lime { + +namespace { + +std::string_view GetSpecificDeviceIDFromDeviceIDListBySerial(const std::vector& list, const std::string& serial) +{ + for (const auto& deviceID : list) + { + if (deviceID.find(serial) != std::string_view::npos) + { + return deviceID; + } + } + + throw std::runtime_error("Serial not found"s); +} + +std::string GetDeviceID(uint16_t vid, uint16_t pid, const std::string& serial) +{ + ULONG bufferSize = 0; + const std::string filter = "USB\\VID_"s + intToHex(vid, true) + "&PID_"s + intToHex(pid, true); + const ULONG flags = CM_GETIDLIST_FILTER_ENUMERATOR; + + CONFIGRET status = CM_Get_Device_ID_List_Size(&bufferSize, filter.c_str(), flags); + if (status == CR_NO_SUCH_VALUE) // no device like that exists, return + { + return ""s; + } + else if (status != CR_SUCCESS) + { + throw std::runtime_error("CM_Get_Device_ID_List_Size failed"s); + } + + const PZZSTR buffer = new CHAR[bufferSize]; + + status = CM_Get_Device_ID_List(filter.c_str(), buffer, bufferSize, flags); + if (status != CR_SUCCESS) + { + throw std::runtime_error("CM_Get_Device_ID_List failed"s); + } + + const std::string_view allDeviceIDs{ buffer, bufferSize }; + const auto& allDeviceIDsSplits = SplitString(allDeviceIDs, "\0"sv); + + const auto deviceID = std::string{ GetSpecificDeviceIDFromDeviceIDListBySerial(allDeviceIDsSplits, serial) }; + + delete[] buffer; + return deviceID; +} + +} // namespace + +WindowsDeviceHotplug::~WindowsDeviceHotplug() +{ + CM_Unregister_Notification(deviceDisconnectCallbackHandle); +} + +void WindowsDeviceHotplug::AddOnHotplugDisconnectCallback(const IUSB::HotplugDisconnectCallbackType& function, void* userData) +{ + hotplugDisconnectCallbacks.push_back({ function, userData }); +} + +void WindowsDeviceHotplug::AddDeviceToReceiveHotplugDisconnectEvents(uint16_t vid, uint16_t pid, const std::string& serial) +{ + const auto deviceID = GetDeviceID(vid, pid, serial); + const auto wideDeviceID = std::wstring_convert>().from_bytes(deviceID); + + CM_NOTIFY_FILTER filter{}; + filter.cbSize = sizeof(filter); + filter.FilterType = CM_NOTIFY_FILTER_TYPE_DEVICEINSTANCE; + std::wcsncpy(filter.u.DeviceInstance.InstanceId, + wideDeviceID.c_str(), + std::min(sizeof(filter.u.DeviceInstance.InstanceId), wideDeviceID.size())); + + auto returnValue = CM_Register_Notification(&filter, this, disconnectCallback, &deviceDisconnectCallbackHandle); + if (returnValue != CR_SUCCESS) + { + throw std::runtime_error("CM_Register_Notification failed with error code "s + intToHex(returnValue)); + } +} + +DWORD CALLBACK WindowsDeviceHotplug::disconnectCallback( + HCMNOTIFICATION hNotify, PVOID Context, CM_NOTIFY_ACTION Action, PCM_NOTIFY_EVENT_DATA EventData, DWORD EventDataSize) +{ + if (CM_NOTIFY_ACTION_DEVICEINSTANCEREMOVED == Action) + { + auto* hotplug = reinterpret_cast(Context); + + for (auto iter = hotplug->hotplugDisconnectCallbacks.rbegin(); iter != hotplug->hotplugDisconnectCallbacks.rend(); ++iter) + { + (*iter)(); + } + } + + return ERROR_SUCCESS; +} + +} // namespace lime diff --git a/src/comms/USB/WindowsDeviceHotplug.h b/src/comms/USB/WindowsDeviceHotplug.h new file mode 100644 index 00000000..95154be7 --- /dev/null +++ b/src/comms/USB/WindowsDeviceHotplug.h @@ -0,0 +1,34 @@ +#ifndef LIME_WINDOWS_DEVICE_HOTPLUG_H +#define LIME_WINDOWS_DEVICE_HOTPLUG_H + +#include "Windows.h" +#include "cfgmgr32.h" + +#include "comms/USB/IUSB.h" + +#include +#include +#include + +namespace lime { + +class WindowsDeviceHotplug +{ + public: + ~WindowsDeviceHotplug(); + + void AddOnHotplugDisconnectCallback(const IUSB::HotplugDisconnectCallbackType& function, void* userData); + + void AddDeviceToReceiveHotplugDisconnectEvents(uint16_t vid, uint16_t pid, const std::string& serial); + + private: + std::vector> hotplugDisconnectCallbacks{}; + HCMNOTIFICATION deviceDisconnectCallbackHandle{}; + + static DWORD CALLBACK disconnectCallback( + HCMNOTIFICATION hNotify, PVOID Context, CM_NOTIFY_ACTION Action, PCM_NOTIFY_EVENT_DATA EventData, DWORD EventDataSize); +}; + +} // namespace lime + +#endif // LIME_WINDOWS_DEVICE_HOTPLUG_H diff --git a/src/comms/USB/WindowsGlobalHotplug.cpp b/src/comms/USB/WindowsGlobalHotplug.cpp new file mode 100644 index 00000000..f80ef130 --- /dev/null +++ b/src/comms/USB/WindowsGlobalHotplug.cpp @@ -0,0 +1,106 @@ +#include "WindowsGlobalHotplug.h" + +#include "Windows.h" +#include "cfgmgr32.h" + +#include "comms/USB/IUSB.h" +#include "comms/USB/GlobalHotplugEvents.h" +#include "CommonFunctions.h" + +#include +#include +#include + +using namespace std::literals::string_literals; +using namespace std::literals::string_view_literals; + +namespace lime { + +namespace { + +uint16_t StringHexToInt(std::wstring_view input) +{ + std::wstringstream ss{ std::wstring(input) }; + + uint16_t value; + ss >> std::hex >> value; + + return value; +} + +IUSB::VendorProductId ParseVidPid(std::wstring_view input) +{ + const auto splits = SplitString(input, L"\\"sv); + + const auto separated = SplitString(splits[1], L"&"sv); + + const auto vidAsString = SplitString(separated[0], L"VID_"sv); + const auto pidAsString = SplitString(separated[1], L"PID_"sv); + + const auto vID = StringHexToInt(vidAsString[0]); + const auto pID = StringHexToInt(pidAsString[0]); + + return { vID, pID }; +} + +} // namespace + +WindowsGlobalHotplug::WindowsGlobalHotplug() +{ + CM_NOTIFY_FILTER filter{}; + filter.cbSize = sizeof(filter); + filter.Flags = CM_NOTIFY_FILTER_FLAG_ALL_DEVICE_INSTANCES; + filter.FilterType = CM_NOTIFY_FILTER_TYPE_DEVICEINSTANCE; + filter.u.DeviceInstance.InstanceId[0] = 0; + auto returnValue = CM_Register_Notification(&filter, nullptr, globalCallback, &globalDeviceDisconnectCallbackHandle); + + if (returnValue != CR_SUCCESS) + { + throw std::runtime_error("CM_Register_Notification failed with error code "s + intToHex(returnValue)); + } +} + +WindowsGlobalHotplug::~WindowsGlobalHotplug() +{ + CM_Unregister_Notification(globalDeviceDisconnectCallbackHandle); +} + +DWORD CALLBACK WindowsGlobalHotplug::globalCallback( + HCMNOTIFICATION hNotify, PVOID Context, CM_NOTIFY_ACTION Action, PCM_NOTIFY_EVENT_DATA EventData, DWORD EventDataSize) +{ + if (EventData->FilterType != CM_NOTIFY_FILTER_TYPE_DEVICEINSTANCE) + { + return ERROR_SUCCESS; + } + + const auto& matchTo = GlobalHotplugEvents::GetVidPids(); + const auto vidPid = ParseVidPid(EventData->u.DeviceInstance.InstanceId); + + // Figure out if it's a device we care about + if (matchTo.find(vidPid) == matchTo.end()) + { + // It's not something we care about, this is the only value we can return here though + return ERROR_SUCCESS; + } + + if (CM_NOTIFY_ACTION_DEVICEINSTANCESTARTED == Action) + { + const auto& callbacks = GlobalHotplugEvents::GetConnectCallbacks(); + for (auto iter = callbacks.rbegin(); iter != callbacks.rend(); ++iter) + { + (*iter)(); + } + } + else if (CM_NOTIFY_ACTION_DEVICEINSTANCEREMOVED == Action) + { + const auto& callbacks = GlobalHotplugEvents::GetDisconnectCallbacks(); + for (auto iter = callbacks.rbegin(); iter != callbacks.rend(); ++iter) + { + (*iter)(); + } + } + + return ERROR_SUCCESS; +} + +} // namespace lime diff --git a/src/comms/USB/WindowsGlobalHotplug.h b/src/comms/USB/WindowsGlobalHotplug.h new file mode 100644 index 00000000..4e006c44 --- /dev/null +++ b/src/comms/USB/WindowsGlobalHotplug.h @@ -0,0 +1,22 @@ +#ifndef LIME_WINDOWS_GLOBAL_HOTPLUG_H +#define LIME_WINDOWS_GLOBAL_HOTPLUG_H + +#include "Windows.h" +#include "cfgmgr32.h" + +namespace lime { +class WindowsGlobalHotplug +{ + public: + WindowsGlobalHotplug(); + ~WindowsGlobalHotplug(); + + private: + HCMNOTIFICATION globalDeviceDisconnectCallbackHandle{}; + + static DWORD CALLBACK globalCallback( + HCMNOTIFICATION hNotify, PVOID Context, CM_NOTIFY_ACTION Action, PCM_NOTIFY_EVENT_DATA EventData, DWORD EventDataSize); +}; +} // namespace lime + +#endif // LIME_WINDOWS_GLOBAL_HOTPLUG_H diff --git a/src/examples/basicRX.cpp b/src/examples/basicRX.cpp index f55f6dec..e9718822 100644 --- a/src/examples/basicRX.cpp +++ b/src/examples/basicRX.cpp @@ -97,7 +97,7 @@ int main(int argc, char** argv) device->StreamSetup(stream, chipIndex); device->StreamStart(chipIndex); - + device->AddHotplugDisconnectCallback([](void* data) { *reinterpret_cast(data) = true; }, &stopProgram); } catch (std::runtime_error& e) { std::cout << "Failed to configure settings: "sv << e.what() << std::endl; diff --git a/src/examples/basicTX.cpp b/src/examples/basicTX.cpp index 213776bd..a96a1ecd 100644 --- a/src/examples/basicTX.cpp +++ b/src/examples/basicTX.cpp @@ -90,7 +90,7 @@ int main(int argc, char** argv) device->StreamSetup(stream, chipIndex); device->StreamStart(chipIndex); - + device->AddHotplugDisconnectCallback([](void* data) { *reinterpret_cast(data) = true; }, &stopProgram); } catch (std::runtime_error& e) { std::cout << "Failed to configure settings: "sv << e.what() << std::endl; diff --git a/src/examples/dualRXTX.cpp b/src/examples/dualRXTX.cpp index 71efe7f7..b6517e8c 100644 --- a/src/examples/dualRXTX.cpp +++ b/src/examples/dualRXTX.cpp @@ -110,7 +110,7 @@ int main(int argc, char** argv) device->StreamSetup(stream, chipIndex); device->StreamStart(chipIndex); - + device->AddHotplugDisconnectCallback([](void* data) { *reinterpret_cast(data) = true; }, &stopProgram); } catch (std::runtime_error& e) { std::cout << "Failed to configure settings: "sv << e.what() << std::endl; diff --git a/src/include/limesuiteng/SDRDevice.h b/src/include/limesuiteng/SDRDevice.h index 34dd1e78..79fe238e 100644 --- a/src/include/limesuiteng/SDRDevice.h +++ b/src/include/limesuiteng/SDRDevice.h @@ -572,6 +572,29 @@ class LIME_API SDRDevice /// @param serialNumber Device's serial number /// @return The operation success state. virtual OpStatus WriteSerialNumber(uint64_t serialNumber); + + /// @brief The type of the function to call when a disconnect event happens. + typedef std::function HotplugDisconnectCallbackType; + + /// @brief The structure to hold the information for the callback + /// @tparam T The type of function to call + template struct CallbackInfo { + T function; + void* userData; + std::size_t id; + + void operator()() const { return function(userData); } + }; + + /// @brief Adds a callback to run when a hotplug disconnect event happens (currently USB only). + /// @param function The function to run when a disconnect happens. + /// @param userData The data to pass into the function when it runs. + /// @return The ID of the callback for removal later. + virtual std::size_t AddHotplugDisconnectCallback(const HotplugDisconnectCallbackType& function, void* userData) = 0; + + /// @brief Removes the given hotplug disconnect callback by ID. + /// @param id The ID of the callback to remove. + virtual void RemoveHotplugDisconnectCallback(std::size_t id) = 0; }; } // namespace lime diff --git a/src/protocols/TRXLooper.cpp b/src/protocols/TRXLooper.cpp index 457d5acf..78dffa90 100644 --- a/src/protocols/TRXLooper.cpp +++ b/src/protocols/TRXLooper.cpp @@ -257,28 +257,35 @@ void TRXLooper::Stop() { lime::error("Failed to join TRXLooper threads"s); } - fpga->StopStreaming(); - uint32_t fpgaTxPktIngressCount; - uint32_t fpgaTxPktDropCounter; - fpga->ReadTxPacketCounters(chipId, &fpgaTxPktIngressCount, &fpgaTxPktDropCounter); - if (mCallback_logMessage) + mStreamEnabled = false; + + try { - char msg[512]; - std::snprintf(msg, - sizeof(msg), - "Tx%i stop: host sent packets: %li (0x%08lX), FPGA packet ingresed: %i (0x%08X), diff: %li, Tx packet dropped: %i", - chipId, - mTx.stats.packets, - mTx.stats.packets, - fpgaTxPktIngressCount, - fpgaTxPktIngressCount, - (mTx.stats.packets & 0xFFFFFFFF) - fpgaTxPktIngressCount, - fpgaTxPktDropCounter); - mCallback_logMessage(LogLevel::Debug, msg); - } + fpga->StopStreaming(); - mStreamEnabled = false; + uint32_t fpgaTxPktIngressCount; + uint32_t fpgaTxPktDropCounter; + fpga->ReadTxPacketCounters(chipId, &fpgaTxPktIngressCount, &fpgaTxPktDropCounter); + if (mCallback_logMessage) + { + char msg[512]; + std::snprintf(msg, + sizeof(msg), + "Tx%i stop: host sent packets: %li (0x%08lX), FPGA packet ingresed: %i (0x%08X), diff: %li, Tx packet dropped: %i", + chipId, + mTx.stats.packets, + mTx.stats.packets, + fpgaTxPktIngressCount, + fpgaTxPktIngressCount, + (mTx.stats.packets & 0xFFFFFFFF) - fpgaTxPktIngressCount, + fpgaTxPktDropCounter); + mCallback_logMessage(LogLevel::Debug, msg); + } + } catch (const std::exception& e) + { + lime::error("%s", e.what()); + } } void TRXLooper::Teardown()