diff --git a/plugin/CMakeLists.txt b/plugin/CMakeLists.txt index d834861d..18b4dad5 100644 --- a/plugin/CMakeLists.txt +++ b/plugin/CMakeLists.txt @@ -26,6 +26,13 @@ find_package(${NAMESPACE}Core REQUIRED) find_package(${NAMESPACE}Plugins REQUIRED) find_package(CURL) +option(ENABLE_POWERMANAGER "Enable PowerManager COMRPC integration for DeepSleep WiFi management" ON) +if(ENABLE_POWERMANAGER) + find_package(${NAMESPACE}Definitions REQUIRED) + add_definitions(-DENABLE_POWERMANAGER) + message("NetworkManager: PowerManager integration enabled") +endif() + if (USE_RDK_LOGGER) find_package(rdklogger REQUIRED) add_definitions(-DUSE_RDK_LOGGER) @@ -83,6 +90,11 @@ add_library(${MODULE_IMPL_NAME} SHARED NetworkManagerLogger.cpp Module.cpp) +if(ENABLE_POWERMANAGER) + target_sources(${MODULE_IMPL_NAME} PRIVATE NetworkManagerPowerClient.cpp) + target_link_libraries(${MODULE_IMPL_NAME} PRIVATE ${NAMESPACE}Definitions::${NAMESPACE}Definitions) +endif() + if(ENABLE_GNOME_NETWORKMANAGER) if(ENABLE_GNOME_GDBUS) message("networkmanager building with gdbus") diff --git a/plugin/NetworkManagerImplementation.cpp b/plugin/NetworkManagerImplementation.cpp index b4b5bca6..ab29e938 100644 --- a/plugin/NetworkManagerImplementation.cpp +++ b/plugin/NetworkManagerImplementation.cpp @@ -20,6 +20,9 @@ #include #include #include "NetworkManagerImplementation.h" +#ifdef ENABLE_POWERMANAGER +#include "NetworkManagerPowerClient.h" +#endif #if USE_TELEMETRY #include "NetworkManagerJsonEnum.h" @@ -54,6 +57,7 @@ namespace WPEFramework m_wlanConnected.store(false); m_ethEnabled.store(false); m_wlanEnabled.store(false); + m_ethDisconnectedForSleep.store(false); /* Set NetworkManager Out-Process name to be NWMgrPlugin */ Core::ProcessInfo().Name("NWMgrPlugin"); @@ -71,6 +75,9 @@ namespace WPEFramework NetworkManagerImplementation::~NetworkManagerImplementation() { NMLOG_INFO("NetworkManager Out-Of-Process Shutdown/Cleanup"); +#ifdef ENABLE_POWERMANAGER + _powerClient.reset(); +#endif connectivityMonitor.stopConnectivityMonitor(); _instance = nullptr; platform_deinit(); @@ -199,6 +206,9 @@ namespace WPEFramework NetworkManagerImplementation::platform_init(); /* change gnome networkmanager or netsrvmgr logg level */ NetworkManagerImplementation::platform_logging(static_cast (config.loglevel.Value())); +#ifdef ENABLE_POWERMANAGER + _powerClient.reset(new NetworkManagerPowerClient(*this)); +#endif return(Core::ERROR_NONE); } @@ -1184,5 +1194,80 @@ namespace WPEFramework } #endif } + +#ifdef ENABLE_POWERMANAGER + void NetworkManagerImplementation::OnPowerModePreChange( + const Exchange::IPowerManager::PowerState currentState, + const Exchange::IPowerManager::PowerState newState, + std::function sendAck) + { + // Called from NetworkManagerPowerClient's power thread. + // Policy (standbyMode check, DeepSleep filter) has already been + // applied by the power thread before calling here. + NMLOG_INFO("OnPowerModePreChange: current=%d new=%d", + static_cast(currentState), static_cast(newState)); + + using PowerState = Exchange::IPowerManager::PowerState; + + if (newState == PowerState::POWER_STATE_STANDBY_DEEP_SLEEP) { + if (m_wlanEnabled.load() && m_wlanConnected.load()) { + NMLOG_INFO("OnPowerModePreChange: going to DeepSleep — disconnecting WiFi"); + uint32_t rcWifiDown = WiFiDisconnect(); + if (rcWifiDown != Core::ERROR_NONE) + NMLOG_WARNING("OnPowerModePreChange: WiFiDisconnect failed (rc=%u)", rcWifiDown); + } + else + { + NMLOG_WARNING("OnPowerModePreChange: going to DeepSleep — WiFi not connected, skipping disconnect"); + } + if (m_ethEnabled.load() && m_ethConnected.load()) { + NMLOG_INFO("OnPowerModePreChange: going to DeepSleep — disconnecting Ethernet"); + uint32_t rcEthDown = EthernetDisconnect(); + if (rcEthDown == Core::ERROR_NONE) { + m_ethDisconnectedForSleep.store(true); + } else { + NMLOG_WARNING("OnPowerModePreChange: EthernetDisconnect failed (rc=%u), will not reconnect on wakeup", rcEthDown); + } + } else { + NMLOG_WARNING("OnPowerModePreChange: going to DeepSleep — Ethernet not connected, skipping disconnect"); + } + } else if (currentState == PowerState::POWER_STATE_STANDBY_DEEP_SLEEP) { + if (m_wlanEnabled.load() && !m_lastConnectedSSID.empty()) { + NMLOG_INFO("OnPowerModePreChange: waking from DeepSleep — reconnecting to '%s'", + m_lastConnectedSSID.c_str()); + uint32_t rcWifiUp = ConnectToKnownSSID(m_lastConnectedSSID); + if (rcWifiUp != Core::ERROR_NONE) + NMLOG_WARNING("OnPowerModePreChange: ConnectToKnownSSID failed (rc=%u)", rcWifiUp); + } else { + NMLOG_WARNING("OnPowerModePreChange: waking from DeepSleep — no last SSID, skipping reconnect"); + } + if (m_ethDisconnectedForSleep.load()) { + m_ethDisconnectedForSleep.store(false); + NMLOG_INFO("OnPowerModePreChange: waking from DeepSleep — reconnecting Ethernet"); + uint32_t rcEthUp = EthernetConnect(); + if (rcEthUp != Core::ERROR_NONE) + NMLOG_WARNING("OnPowerModePreChange: EthernetConnect failed (rc=%u)", rcEthUp); + } else { + NMLOG_WARNING("OnPowerModePreChange: waking from DeepSleep — Ethernet was not disconnected for sleep, skipping reconnect"); + } + } + sendAck(); + } + + void NetworkManagerImplementation::OnPowerModeChanged( + const Exchange::IPowerManager::PowerState currentState, + const Exchange::IPowerManager::PowerState newState) + { + NMLOG_INFO("OnPowerModeChanged: current=%d new=%d", + static_cast(currentState), static_cast(newState)); + if (currentState == Exchange::IPowerManager::POWER_STATE_STANDBY_DEEP_SLEEP) { + // Waking from DeepSleep with Network Standby ON: the AP may have + // changed channel while the device slept (802.11 CSA). Trigger an + // active scan so the driver discovers the AP on its new channel. + NMLOG_INFO("OnPowerModeChanged: waking from DeepSleep, triggering active WiFi scan"); + StartWiFiScan("", nullptr); + } + } +#endif // ENABLE_POWERMANAGER } } diff --git a/plugin/NetworkManagerImplementation.h b/plugin/NetworkManagerImplementation.h index f5bd49b1..919526b9 100644 --- a/plugin/NetworkManagerImplementation.h +++ b/plugin/NetworkManagerImplementation.h @@ -34,6 +34,9 @@ using namespace std; #include "NetworkManagerLogger.h" #include "NetworkManagerConnectivity.h" #include "NetworkManagerStunClient.h" +#ifdef ENABLE_POWERMANAGER +#include "NetworkManagerPowerClient.h" +#endif /* Forward declarations to avoid pulling GLib/libnm headers into this header */ typedef struct _NMClient NMClient; @@ -63,6 +66,9 @@ namespace WPEFramework namespace Plugin { class NetworkManagerImplementation : public Exchange::INetworkManager +#ifdef ENABLE_POWERMANAGER + , public INetworkPowerCallback +#endif { enum NetworkEvents { @@ -226,6 +232,8 @@ namespace WPEFramework uint32_t WiFiConnect(const WiFiConnectTo& ssid /* @in */) override; uint32_t WiFiDisconnect(void) override; + uint32_t EthernetDisconnect(void); + uint32_t EthernetConnect(void); uint32_t GetConnectedSSID(WiFiSSIDInfo& ssidInfo /* @out */) override; uint32_t StartWPS(const WiFiWPS& method /* @in */, const string& wps_pin /* @in */) override; @@ -277,6 +285,15 @@ namespace WPEFramework void ReportWiFiSignalQualityChange(const string ssid, const int strength, const int noise, const int snr, const Exchange::INetworkManager::WiFiSignalQuality quality); void logTelemetry(const std::string& eventName, const std::string& message); +#ifdef ENABLE_POWERMANAGER + // INetworkPowerCallback overrides + void OnPowerModePreChange(const Exchange::IPowerManager::PowerState currentState, + const Exchange::IPowerManager::PowerState newState, + std::function sendAck) override; + void OnPowerModeChanged(const Exchange::IPowerManager::PowerState currentState, + const Exchange::IPowerManager::PowerState newState) override; +#endif + private: void platform_init(void); void platform_deinit(void); @@ -314,6 +331,9 @@ namespace WPEFramework std::atomic m_stopThread{false}; std::mutex m_condVariableMutex; std::condition_variable m_condVariable; +#ifdef ENABLE_POWERMANAGER + std::unique_ptr _powerClient; +#endif public: IPAddress m_ethIPv4Address; IPAddress m_wlanIPv4Address; @@ -323,6 +343,7 @@ namespace WPEFramework std::atomic m_wlanConnected; std::atomic m_ethEnabled; std::atomic m_wlanEnabled; + std::atomic m_ethDisconnectedForSleep; std::string m_lastConnectedSSID; NMClient *m_nmClient{nullptr}; /* proxy NMClient — bound to m_nmContext */ GMainContext *m_nmContext{nullptr}; /* isolated context, not the global default */ diff --git a/plugin/NetworkManagerPowerClient.cpp b/plugin/NetworkManagerPowerClient.cpp new file mode 100644 index 00000000..92c3be06 --- /dev/null +++ b/plugin/NetworkManagerPowerClient.cpp @@ -0,0 +1,296 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2024 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#ifdef ENABLE_POWERMANAGER + +#include "NetworkManagerPowerClient.h" +#include "NetworkManagerLogger.h" +#include + +using namespace WPEFramework; +using namespace WPEFramework::Exchange; +using namespace WPEFramework::Plugin; + +// --------------------------------------------------------------------------- +// NetworkManagerPowerClient +// --------------------------------------------------------------------------- + +NetworkManagerPowerClient::NetworkManagerPowerClient(INetworkPowerCallback& callback) + : mCallback(callback) + , mPreChangeNotification(*this) + , mChangedNotification(*this) +{ + NMLOG_INFO("NetworkManagerPowerClient: connecting to PowerManager"); + if (auto r = Open(RPC::CommunicationTimeOut, Connector(), "org.rdk.PowerManager"); r == Core::ERROR_NONE) { + // Connected; Operational() will be called by the framework when the proxy is ready + } else { + NMLOG_ERROR("NetworkManagerPowerClient: failed to open link to PowerManager (error %u)", r); + } +} + +NetworkManagerPowerClient::~NetworkManagerPowerClient() +{ + NMLOG_INFO("NetworkManagerPowerClient: shutting down"); + // Stop the power-event thread first so any in-flight work completes + // before we release the COM-RPC proxy. + mStopThread = true; + mQueueCv.notify_one(); + if (mPowerThread.joinable()) { + mPowerThread.join(); + } + Close(Core::infinite); + unregisterEventsAndDeactivate(); +} + +bool NetworkManagerPowerClient::IsValid() const +{ + return mPowerManager != nullptr; +} + +bool NetworkManagerPowerClient::getNetworkStandbyMode() const +{ + bool standbyMode = false; + if (IsValid()) { + if (auto r = mPowerManager->GetNetworkStandbyMode(standbyMode); r != Core::ERROR_NONE) { + NMLOG_ERROR("NetworkManagerPowerClient: GetNetworkStandbyMode failed (%u)", r); + } + } + return standbyMode; +} + +void NetworkManagerPowerClient::sendPowerModePreChangeComplete(int transactionId) +{ + if (IsValid()) { + // Return value intentionally ignored; a stale transactionId is harmless + mPowerManager->PowerModePreChangeComplete(mClientId, transactionId); + } +} + +void NetworkManagerPowerClient::sendDelayPowerModeChange(int transactionId, int seconds) +{ + if (IsValid()) { + if (auto r = mPowerManager->DelayPowerModeChangeBy(mClientId, transactionId, seconds); r != Core::ERROR_NONE) { + NMLOG_ERROR("NetworkManagerPowerClient: DelayPowerModeChangeBy failed (%u)", r); + } + } +} + +void NetworkManagerPowerClient::Operational(bool upAndRunning) +{ + NMLOG_INFO("NetworkManagerPowerClient::Operational(%s)", upAndRunning ? "true" : "false"); + if (upAndRunning) { + if (!IsValid()) { + mPowerManager = Interface(); + registerEvents(); + // Start the dedicated power-event thread after registration so it + // is ready to handle events as soon as they can arrive. + mStopThread = false; + mPowerThread = std::thread(&NetworkManagerPowerClient::powerThreadLoop, this); + } + } else { + // Stop the power-event thread before unregistering so any in-flight + // event that was already enqueued is drained with a fast ack. + mStopThread = true; + mQueueCv.notify_one(); + if (mPowerThread.joinable()) { + mPowerThread.join(); + } + unregisterEventsAndDeactivate(); + } +} + +void NetworkManagerPowerClient::registerEvents() +{ + NMLOG_INFO("NetworkManagerPowerClient: registering events"); + if (!IsValid()) { + NMLOG_ERROR("NetworkManagerPowerClient: not in valid state, skipping event registration"); + return; + } + if (auto r = mPowerManager->AddPowerModePreChangeClient("org.rdk.NetworkManager", mClientId); r != Core::ERROR_NONE) { + NMLOG_ERROR("NetworkManagerPowerClient: AddPowerModePreChangeClient failed (%u)", r); + } else { + NMLOG_INFO("NetworkManagerPowerClient: registered as pre-change client, mClientId=%u", mClientId); + } + if (auto r = mPowerManager->Register(&mPreChangeNotification); r != Core::ERROR_NONE) { + NMLOG_ERROR("NetworkManagerPowerClient: Register(preChange) failed (%u)", r); + } + if (auto r = mPowerManager->Register(&mChangedNotification); r != Core::ERROR_NONE) { + NMLOG_ERROR("NetworkManagerPowerClient: Register(changed) failed (%u)", r); + } +} + +void NetworkManagerPowerClient::unregisterEvents() +{ + NMLOG_INFO("NetworkManagerPowerClient: unregistering events"); + if (!IsValid()) { + NMLOG_ERROR("NetworkManagerPowerClient: not in valid state, skipping event unregistration"); + return; + } + // NOTE: RemovePowerModePreChangeClient MUST be called before Unregister(IModePreChangeNotification) + // per the IPowerManager API contract. + if (auto r = mPowerManager->RemovePowerModePreChangeClient(mClientId); r != Core::ERROR_NONE) { + NMLOG_ERROR("NetworkManagerPowerClient: RemovePowerModePreChangeClient failed (%u)", r); + } + if (auto r = mPowerManager->Unregister(&mPreChangeNotification); r != Core::ERROR_NONE) { + NMLOG_ERROR("NetworkManagerPowerClient: Unregister(preChange) failed (%u)", r); + } + if (auto r = mPowerManager->Unregister(&mChangedNotification); r != Core::ERROR_NONE) { + NMLOG_ERROR("NetworkManagerPowerClient: Unregister(changed) failed (%u)", r); + } +} + +void NetworkManagerPowerClient::unregisterEventsAndDeactivate() +{ + if (IsValid()) { + unregisterEvents(); + mPowerManager->Release(); + mPowerManager = nullptr; + } +} + +// --------------------------------------------------------------------------- +// Power event thread +// --------------------------------------------------------------------------- + +void NetworkManagerPowerClient::powerThreadLoop() +{ + NMLOG_INFO("NetworkManagerPowerClient: power event thread started"); + while (true) { + PowerEvent event{}; + { + std::unique_lock lock(mQueueMutex); + mQueueCv.wait(lock, [this]{ return !mEventQueue.empty() || mStopThread.load(); }); + + if (mStopThread) { + // Drain remaining events with fast acks before exiting so + // PowerManager is never left waiting on a stale transaction. + // CHANGED events have no ack protocol — skip them. + std::vector pending; + while (!mEventQueue.empty()) { + pending.push_back(mEventQueue.front()); + mEventQueue.pop(); + } + lock.unlock(); + for (const auto& e : pending) { + if (e.type == PowerEvent::EventType::PRE_CHANGE) { + sendPowerModePreChangeComplete(e.transactionId); + } + } + break; + } + + event = mEventQueue.front(); + mEventQueue.pop(); + } + // Lock released — process event on this thread (blocking is fine here) + + if (event.type == PowerEvent::EventType::CHANGED) { + // Wakeup notification — no ack required. + const bool fromDeepSleep = (event.currentState == PowerState::POWER_STATE_STANDBY_DEEP_SLEEP); + if (fromDeepSleep && event.standbyMode) { + NMLOG_INFO("NetworkManagerPowerClient: power thread — wakeup from DeepSleep standby ON, triggering WiFi scan"); + mCallback.OnPowerModeChanged(event.currentState, event.newState); + } else { + NMLOG_INFO("NetworkManagerPowerClient: power thread — CHANGED event, no action (fromDeepSleep=%d standbyMode=%d)", + fromDeepSleep, event.standbyMode); + } + continue; + } + + // PRE_CHANGE event processing below + auto sendAck = [transactionId = event.transactionId, this]() { + sendPowerModePreChangeComplete(transactionId); + }; + + const bool toDeepSleep = (event.newState == PowerState::POWER_STATE_STANDBY_DEEP_SLEEP); + const bool fromDeepSleep = (event.currentState == PowerState::POWER_STATE_STANDBY_DEEP_SLEEP); + + if ((toDeepSleep || fromDeepSleep) && !event.standbyMode) { + // Deep-sleep transition with Network Standby OFF: delegate to + // NetworkManagerImplementation (WiFiDisconnect / reconnect) which + // will call sendAck() when done. + NMLOG_INFO("NetworkManagerPowerClient: power thread — %s DeepSleep standby OFF", + toDeepSleep ? "to" : "from"); + mCallback.OnPowerModePreChange(event.currentState, event.newState, sendAck); + } else { + // standby ON or non-DeepSleep: no WiFi action needed, ack immediately. + NMLOG_INFO("NetworkManagerPowerClient: power thread — fast-path ack (standbyMode=%d toDeepSleep=%d fromDeepSleep=%d)", + event.standbyMode, toDeepSleep, fromDeepSleep); + sendAck(); + } + } + NMLOG_INFO("NetworkManagerPowerClient: power event thread stopped"); +} + +// --------------------------------------------------------------------------- +// PreChangeNotification +// --------------------------------------------------------------------------- + +void NetworkManagerPowerClient::PreChangeNotification::OnPowerModePreChange( + const PowerState currentState, const PowerState newState, + const int transactionId, const int stateChangeAfter) +{ + NMLOG_INFO("NetworkManagerPowerClient::OnPowerModePreChange current=%d new=%d txId=%d after=%ds", + static_cast(currentState), static_cast(newState), transactionId, stateChangeAfter); + + // Query standby mode inline on the COM-RPC dispatcher thread — it is a + // COM-RPC call and must not be made from the power thread. + const bool standbyMode = mClient.getNetworkStandbyMode(); + + // Cache for use by ChangedNotification + mClient.mLastChangeStandbyMode = standbyMode; + + // Reserve a delay window now (before returning) so PowerManager knows to + // wait at least 5 s. Only needed when we will actually disconnect WiFi. + if (newState == PowerState::POWER_STATE_STANDBY_DEEP_SLEEP && !standbyMode) { + mClient.sendDelayPowerModeChange(transactionId, 5); + } + + // Enqueue and return immediately — the power thread does the real work. + { + std::lock_guard lock(mClient.mQueueMutex); + mClient.mEventQueue.push(PowerEvent{PowerEvent::EventType::PRE_CHANGE, + currentState, newState, standbyMode, transactionId}); + } + mClient.mQueueCv.notify_one(); +} + +// --------------------------------------------------------------------------- +// ChangedNotification +// --------------------------------------------------------------------------- + +void NetworkManagerPowerClient::ChangedNotification::OnPowerModeChanged( + const PowerState currentState, const PowerState newState) +{ + NMLOG_INFO("NetworkManagerPowerClient::OnPowerModeChanged current=%d new=%d", + static_cast(currentState), static_cast(newState)); + + // Use the standby mode cached + const bool standbyMode = mClient.mLastChangeStandbyMode; + + // Enqueue and return immediately so the COM-RPC dispatcher thread is freed. + { + std::lock_guard lock(mClient.mQueueMutex); + mClient.mEventQueue.push(PowerEvent{PowerEvent::EventType::CHANGED, + currentState, newState, standbyMode, 0}); + } + mClient.mQueueCv.notify_one(); +} + +#endif // ENABLE_POWERMANAGER diff --git a/plugin/NetworkManagerPowerClient.h b/plugin/NetworkManagerPowerClient.h new file mode 100644 index 00000000..14009200 --- /dev/null +++ b/plugin/NetworkManagerPowerClient.h @@ -0,0 +1,184 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2024 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#pragma once + +#ifdef ENABLE_POWERMANAGER + +#include "Module.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace WPEFramework { +namespace Plugin { + +/** + * Callback interface that decouples NetworkManagerPowerClient from + * NetworkManagerImplementation. The implementation receives power state + * transitions and must call sendAck() exactly once per OnPowerModePreChange. + */ +struct INetworkPowerCallback { + virtual ~INetworkPowerCallback() = default; + + /** + * Called when a power mode pre-change event arrives that involves + * POWER_STATE_STANDBY_DEEP_SLEEP (either as the new state or the current + * state). The implementation MUST call sendAck() exactly once. + */ + virtual void OnPowerModePreChange(const Exchange::IPowerManager::PowerState currentState, + const Exchange::IPowerManager::PowerState newState, + std::function sendAck) = 0; + + /** + * Called when a power mode changed event arrives (informational only; + * no ack required). + */ + virtual void OnPowerModeChanged(const Exchange::IPowerManager::PowerState currentState, + const Exchange::IPowerManager::PowerState newState) = 0; +}; + +/** + * Thunder COMRPC client to the PowerManager plugin. + * + * Follows the same pattern as Mediarite's PowerManagerPluginClient: + * - Inherits SmartInterfaceType for automatic + * reconnect / Operational() lifecycle callbacks. + * - Registers as an AddPowerModePreChangeClient so it participates + * in the pre-change ack protocol. + * - Delegates DeepSleep transitions to INetworkPowerCallback. + * - Sends a fast-path PowerModePreChangeComplete for all other transitions. + * + * Lifecycle: + * Construction → Open() connects to PowerManager (async). + * Operational(true) → registers events; IsValid() returns true. + * Operational(false) → unregisters events and releases proxy. + * Destruction → Close() then unregisterEventsAndDeactivate(). + */ +class NetworkManagerPowerClient : protected RPC::SmartInterfaceType { +public: + using PowerState = Exchange::IPowerManager::PowerState; + + explicit NetworkManagerPowerClient(INetworkPowerCallback& callback); + ~NetworkManagerPowerClient() override; + + NetworkManagerPowerClient(const NetworkManagerPowerClient&) = delete; + NetworkManagerPowerClient& operator=(const NetworkManagerPowerClient&) = delete; + + /** Returns true when the PowerManager COMRPC proxy is available. */ + bool IsValid() const; + + /** Queries the current Network Standby mode from PowerManager. */ + bool getNetworkStandbyMode() const; + + /** Sends PowerModePreChangeComplete to PowerManager; ignores return value. */ + void sendPowerModePreChangeComplete(int transactionId); + + /** Requests a delay window extension via DelayPowerModeChangeBy. */ + void sendDelayPowerModeChange(int transactionId, int seconds); + +private: + // ----------------------------------------------------------------------- + // IModePreChangeNotification sink + // ----------------------------------------------------------------------- + class PreChangeNotification : public Exchange::IPowerManager::IModePreChangeNotification { + public: + explicit PreChangeNotification(NetworkManagerPowerClient& client) + : mClient(client) {} + + void OnPowerModePreChange(const PowerState currentState, const PowerState newState, + const int transactionId, const int stateChangeAfter) override; + + BEGIN_INTERFACE_MAP(PreChangeNotification) + INTERFACE_ENTRY(Exchange::IPowerManager::IModePreChangeNotification) + END_INTERFACE_MAP + + private: + NetworkManagerPowerClient& mClient; + }; + + // ----------------------------------------------------------------------- + // IModeChangedNotification sink + // ----------------------------------------------------------------------- + class ChangedNotification : public Exchange::IPowerManager::IModeChangedNotification { + public: + explicit ChangedNotification(NetworkManagerPowerClient& client) : mClient(client) {} + + void OnPowerModeChanged(const PowerState currentState, const PowerState newState) override; + + BEGIN_INTERFACE_MAP(ChangedNotification) + INTERFACE_ENTRY(Exchange::IPowerManager::IModeChangedNotification) + END_INTERFACE_MAP + + private: + NetworkManagerPowerClient& mClient; + }; + + // ----------------------------------------------------------------------- + // SmartInterfaceType lifecycle callback + // ----------------------------------------------------------------------- + void Operational(bool upAndRunning) override; + + void registerEvents(); + void unregisterEvents(); + void unregisterEventsAndDeactivate(); + + // ----------------------------------------------------------------------- + // Members + // ----------------------------------------------------------------------- + INetworkPowerCallback& mCallback; + Exchange::IPowerManager* mPowerManager{nullptr}; + Core::Sink mPreChangeNotification; + Core::Sink mChangedNotification; + uint32_t mClientId{0}; + + // Power-event thread: receives events enqueued by the COM-RPC dispatcher + // thread and processes them (WiFiDisconnect etc.) without blocking the + // dispatcher. + struct PowerEvent { + enum class EventType { PRE_CHANGE, CHANGED }; + + EventType type; + PowerState currentState; + PowerState newState; + bool standbyMode; + int transactionId; + }; + + std::thread mPowerThread; + std::queue mEventQueue; + std::mutex mQueueMutex; + std::condition_variable mQueueCv; + std::atomic mStopThread{false}; + + // Cached standby mode from the last PRE_CHANGE event. + bool mLastChangeStandbyMode{false}; + + void powerThreadLoop(); +}; + +} // namespace Plugin +} // namespace WPEFramework + +#endif // ENABLE_POWERMANAGER diff --git a/plugin/gnome/NetworkManagerGnomeProxy.cpp b/plugin/gnome/NetworkManagerGnomeProxy.cpp index b661a8de..a1a91de0 100644 --- a/plugin/gnome/NetworkManagerGnomeProxy.cpp +++ b/plugin/gnome/NetworkManagerGnomeProxy.cpp @@ -1118,6 +1118,22 @@ namespace WPEFramework return rc; } + uint32_t NetworkManagerImplementation::EthernetDisconnect(void) + { + uint32_t rc = Core::ERROR_GENERAL; + if(wifi->ethernetDisconnect()) + rc = Core::ERROR_NONE; + return rc; + } + + uint32_t NetworkManagerImplementation::EthernetConnect(void) + { + uint32_t rc = Core::ERROR_GENERAL; + if(wifi->activateKnownConnection(nmUtils::ethIface(), "Wired connection 1")) + rc = Core::ERROR_NONE; + return rc; + } + uint32_t NetworkManagerImplementation::GetConnectedSSID(WiFiSSIDInfo& ssidInfo /* @out */) { uint32_t rc = Core::ERROR_RPC_CALL_FAILED; diff --git a/plugin/gnome/NetworkManagerGnomeWIFI.cpp b/plugin/gnome/NetworkManagerGnomeWIFI.cpp index 2348124d..5364cece 100644 --- a/plugin/gnome/NetworkManagerGnomeWIFI.cpp +++ b/plugin/gnome/NetworkManagerGnomeWIFI.cpp @@ -45,7 +45,11 @@ namespace WPEFramework wifiManager::wifiManager() : m_client(nullptr), m_loop(nullptr), m_createNewConnection(false), m_objectPath(nullptr), m_wifidevice(nullptr), m_source(nullptr), m_cancellable(nullptr){ NMLOG_INFO("wifiManager"); m_nmContext = g_main_context_new(); - g_main_context_push_thread_default(m_nmContext); + // g_main_context_push_thread_default(m_nmContext); + // Do NOT push m_nmContext here. Pushing here permanently locks ownership + // to the constructor thread (owner_count stays at 1, never released). + // All callers — including power-event threads — must push/pop around + // each createClientNewConnection()/deleteClientConnection() pair instead. m_loop = g_main_loop_new(m_nmContext, FALSE); } @@ -53,12 +57,21 @@ namespace WPEFramework { GError *error = NULL; + // Serialize concurrent wifi operations from different threads + m_opMutex.lock(); + // Push our private context as thread-default so nm_client_new (and any + // internal g_dbus_proxy_new_sync it calls) uses m_nmContext instead of + // the global default context, which is owned by nm_event_thrd. + g_main_context_push_thread_default(m_nmContext); + m_client = nm_client_new(NULL, &error); if (!m_client || !m_loop) { if (error) { NMLOG_ERROR("Could not connect to NetworkManager: %s.", error->message); g_error_free(error); } + g_main_context_pop_thread_default(m_nmContext); + m_opMutex.unlock(); return false; } @@ -102,6 +115,11 @@ namespace WPEFramework g_free(m_objectPath); m_objectPath = NULL; } + + // Pop the context pushed in createClientNewConnection() + g_main_context_pop_thread_default(m_nmContext); + // Release operation lock acquired in createClientNewConnection() + m_opMutex.unlock(); } bool wifiManager::quit(NMDevice *wifiNMDevice) @@ -407,6 +425,34 @@ namespace WPEFramework return m_isSuccess; } + bool wifiManager::ethernetDisconnect() + { + NMDeviceState deviceState = NM_DEVICE_STATE_UNKNOWN; + if(!createClientNewConnection()) + return false; + + NMDevice *ethDevice = nm_client_get_device_by_iface(m_client, nmUtils::ethIface()); + if(ethDevice == NULL) { + NMLOG_WARNING("ethernet device not found !"); + deleteClientConnection(); + return true; + } + + deviceState = nm_device_get_state(ethDevice); + NMLOG_DEBUG("ethernet device current state is %d !", deviceState); + if (deviceState <= NM_DEVICE_STATE_DISCONNECTED || deviceState == NM_DEVICE_STATE_FAILED || deviceState == NM_DEVICE_STATE_DEACTIVATING) + { + NMLOG_WARNING("ethernet already disconnected !"); + deleteClientConnection(); + return true; + } + + nm_device_disconnect_async(ethDevice, m_cancellable, disconnectCb, this); + wait(m_loop); + deleteClientConnection(); + return m_isSuccess; + } + static NMAccessPoint* findMatchingSSID(const GPtrArray* ApList, Exchange::INetworkManager::WiFiConnectTo& ssidInfo) { NMAccessPoint *AccessPoint = nullptr; diff --git a/plugin/gnome/NetworkManagerGnomeWIFI.h b/plugin/gnome/NetworkManagerGnomeWIFI.h index 51164aa5..1548d8d5 100644 --- a/plugin/gnome/NetworkManagerGnomeWIFI.h +++ b/plugin/gnome/NetworkManagerGnomeWIFI.h @@ -51,6 +51,7 @@ namespace WPEFramework bool getWifiState(Exchange::INetworkManager::WiFiState& state); bool wifiDisconnect(); + bool ethernetDisconnect(); bool activateKnownConnection(std::string iface, std::string knowConnectionID=""); bool wifiConnectedSSIDInfo(Exchange::INetworkManager::WiFiSSIDInfo &ssidinfo); bool wifiConnect(const Exchange::INetworkManager::WiFiConnectTo &ssidInfo); @@ -106,6 +107,7 @@ namespace WPEFramework GSource *m_source; GCancellable *m_cancellable; std::mutex m_cancellableMutex; + std::mutex m_opMutex; // serializes concurrent wifi operations from different threads bool m_isSuccess = false; SecretAgent m_secretAgent; };