diff --git a/idd/LGIdd/display/CDeviceContext.cpp b/idd/LGIdd/display/CDeviceContext.cpp index ebb6b2fa..f5dfde38 100644 --- a/idd/LGIdd/display/CDeviceContext.cpp +++ b/idd/LGIdd/display/CDeviceContext.cpp @@ -385,16 +385,42 @@ void CDeviceContext::FinishAdapterInit(UINT connectorIndex) DEBUG_INFO("Preferred render adapter set"); } - FinishInit(connectorIndex); + bool monitorDisabled; + bool arrivalPending; + { + CSRWExclusiveLock lock(m_localRecoveryLock); + m_adapterReady = true; + monitorDisabled = m_recoveryMonitorDisabled; + arrivalPending = m_localRecoveryArrival; + } + + if (!monitorDisabled) + { + FinishInit(connectorIndex); + return; + } + + if (arrivalPending) + m_monitorManager.Enable(); + else + { + // Recovery can intentionally disable the monitor before the adapter's + // first arrival. This is still a valid synchronization boundary: allow a + // later NORMAL request to re-enable the monitor instead of waiting for an + // arrival that ACTIVE deliberately suppressed. + m_transport->SyncRecovery(); + } } void CDeviceContext::FinishInit(UINT connectorIndex) { CDisplayConfiguration::Description description = m_displayConfiguration.GetDescription(); - if (m_monitorManager.Create( - connectorIndex, m_adapter, std::move(description.edid), this)) + const bool arrived = m_monitorManager.Create( + connectorIndex, m_adapter, std::move(description.edid), this); + if (arrived) m_transport->SyncRecovery(); + CompleteRecoveryArrival(arrived); } void CDeviceContext::ReplugMonitor() @@ -489,28 +515,80 @@ bool CDeviceContext::InitializeTransport() g_pipe.SetRecoveryHandler( [](void * opaque, uint64_t route, uint64_t session, - uint32_t serial, bool active, LGPipeMsg::Type result) + uint32_t serial, bool active, CPipeServer::RecoveryResult result) { CDeviceContext * context = static_cast(opaque); + RecoveryAction action; + action.route = route; + action.session = session; + action.serial = serial; + action.active = active; + + if (result == CPipeServer::RecoveryResult::HELPER_UNAVAILABLE) + { + // Pipe recovery messages do not carry the local operation deadline. + // Recover it from the canonical action so a late monitor transition + // cannot complete an operation after the recovery hub timed it out. + { + CSRWSharedLock lock(context->m_localRecoveryLock); + if (context->m_latestRecoveryValid && + context->SameRecoveryAction(action, + context->m_latestRecoveryAction)) + action = context->m_latestRecoveryAction; + } + + if (!context->QueueLocalRecovery(action)) + context->m_transport->RecoveryStatus( + route, session, serial, active, + ITransport::Recovery::FAILED, RPC_S_SERVER_UNAVAILABLE); + return; + } + + std::lock_guard transitionLock( + context->m_recoveryTransitionMutex); + context->m_helperRecoveryAction = action; + context->m_helperRecoveryComplete = true; + context->RemoveLocalRecovery(action); + ITransport::Recovery state = ITransport::Recovery::FAILED; uint32_t error = ERROR_SUCCESS; switch (result) { - case LGPipeMsg::RECOVERY_OFF: + case CPipeServer::RecoveryResult::NORMAL: state = ITransport::Recovery::NORMAL; + { + CSRWExclusiveLock lock(context->m_localRecoveryLock); + if (context->m_latestRecoveryValid && + context->SameRecoveryAction(action, + context->m_latestRecoveryAction)) + { + context->m_recoveryActive = false; + context->m_latestRecoveryAction.deadline = 0; + } + } break; - case LGPipeMsg::RECOVERY_ON: + case CPipeServer::RecoveryResult::ACTIVE: state = ITransport::Recovery::ACTIVE; + { + CSRWExclusiveLock lock(context->m_localRecoveryLock); + if (context->m_latestRecoveryValid && + context->SameRecoveryAction(action, + context->m_latestRecoveryAction)) + { + context->m_recoveryActive = true; + context->m_latestRecoveryAction.deadline = 0; + } + } break; - case LGPipeMsg::RECOVERY_FAILED: + case CPipeServer::RecoveryResult::FAILED: error = ERROR_GEN_FAILURE; break; - case LGPipeMsg::RECOVERY_NO_DISPLAY: + case CPipeServer::RecoveryResult::NO_DISPLAY: error = ERROR_NOT_FOUND; break; @@ -588,6 +666,8 @@ bool CDeviceContext::SetupTransport(size_t alignSize) void CDeviceContext::TransportTimer() { + ProcessLocalRecovery(); + // Monitor work is deferred off IddCx callback threads. switch (m_monitorManager.TakeDeferredAction()) { @@ -623,6 +703,360 @@ InteractionResult CDeviceContext::OnSetResolution(const SourceKey& source, bool CDeviceContext::OnRecoveryAction(const RecoveryAction& action) { - return g_pipe.SetRecovery(this, action.route, action.session, - action.serial, action.active); + bool recoveryActive; + bool monitorDisabled; + { + std::lock_guard transitionLock(m_recoveryTransitionMutex); + CSRWExclusiveLock lock(m_localRecoveryLock); + m_latestRecoveryAction = action; + m_latestRecoveryValid = true; + recoveryActive = m_recoveryActive; + monitorDisabled = m_recoveryMonitorDisabled; + } + + // The monitor must exist before Helper can restore the LG display path. + // Re-arrive it first when recovery previously used the IDD-only fallback. + if (!action.active && monitorDisabled) + return QueueLocalRecovery(action); + + const CPipeServer::RecoveryDispatch dispatch = g_pipe.SetRecovery( + this, action.route, action.session, action.serial, action.active, + action.active || !recoveryActive); + + std::lock_guard transitionLock(m_recoveryTransitionMutex); + if (m_helperRecoveryComplete && + SameRecoveryAction(m_helperRecoveryAction, action)) + return true; + + switch (dispatch) + { + case CPipeServer::RecoveryDispatch::SENT: + return true; + + case CPipeServer::RecoveryDispatch::UNAVAILABLE: + case CPipeServer::RecoveryDispatch::QUEUED: + return QueueLocalRecovery(action); + + case CPipeServer::RecoveryDispatch::REJECTED: + return false; + } + return false; +} + +bool CDeviceContext::SameRecoveryAction( + const RecoveryAction& left, const RecoveryAction& right) +{ + return left.route == right.route && + left.session == right.session && + left.serial == right.serial && + left.active == right.active; +} + +bool CDeviceContext::QueueLocalRecovery(const RecoveryAction& action) +{ + if (!action.route || !action.session || !action.serial) + return false; + + CSRWExclusiveLock lock(m_localRecoveryLock); + if (m_latestRecoveryValid && + !SameRecoveryAction(action, m_latestRecoveryAction)) + return true; + + if (m_localRecoveryArrival) + { + const RecoveryAction& pending = m_localRecoveryArrivalAction; + if (SameRecoveryAction(pending, action)) + return true; + } + + for (const RecoveryAction& pending : m_localRecoveryQueue) + if (SameRecoveryAction(pending, action)) + return true; + + m_localRecoveryQueue.push_back(action); + return true; +} + +void CDeviceContext::RemoveLocalRecovery(const RecoveryAction& action) +{ + CSRWExclusiveLock lock(m_localRecoveryLock); + for (auto it = m_localRecoveryQueue.begin(); + it != m_localRecoveryQueue.end();) + { + if (SameRecoveryAction(*it, action)) + it = m_localRecoveryQueue.erase(it); + else + ++it; + } +} + +void CDeviceContext::ProcessLocalRecovery() +{ + std::lock_guard transitionLock(m_recoveryTransitionMutex); + + RecoveryAction action; + bool haveAction = false; + bool reconcileActive = false; + bool reconcileAdapterReady = false; + { + CSRWExclusiveLock lock(m_localRecoveryLock); + for (;;) + { + if (m_localRecoveryQueue.empty()) + break; + action = m_localRecoveryQueue.front(); + m_localRecoveryQueue.pop_front(); + const bool current = !m_latestRecoveryValid || + SameRecoveryAction(action, m_latestRecoveryAction); + const bool expired = action.deadline && + GetTickCount64() >= action.deadline; + if (current && !expired) + { + haveAction = true; + break; + } + + // The request can expire after Helper disappears but before this timer + // consumes the handoff. Its result is stale, but an already-established + // ACTIVE state still needs the IDD monitor physically disabled. + if (current && expired && m_recoveryActive && + !m_recoveryMonitorDisabled) + { + m_recoveryMonitorDisabled = true; + reconcileActive = true; + reconcileAdapterReady = m_adapterReady; + break; + } + } + } + + if (reconcileActive) + { + DEBUG_WARN( + "IDD Helper is unavailable; reconciling the active recovery topology"); + if (!m_monitorManager.Disable()) + { + CSRWExclusiveLock lock(m_localRecoveryLock); + m_recoveryMonitorDisabled = false; + } + else if (reconcileAdapterReady) + m_transport->SyncRecovery(); + return; + } + + if (!haveAction) + return; + + if (action.active) + { + DEBUG_WARN( + "IDD Helper is unavailable; disabling the virtual monitor for recovery"); + bool oldRecoveryActive; + bool oldMonitorDisabled; + bool adapterReady; + { + CSRWExclusiveLock lock(m_localRecoveryLock); + oldRecoveryActive = m_recoveryActive; + oldMonitorDisabled = m_recoveryMonitorDisabled; + m_recoveryActive = true; + m_recoveryMonitorDisabled = true; + adapterReady = m_adapterReady; + } + + const bool disabled = m_monitorManager.Disable(); + if (!disabled) + { + CSRWExclusiveLock lock(m_localRecoveryLock); + m_recoveryActive = oldRecoveryActive; + m_recoveryMonitorDisabled = oldMonitorDisabled; + } + if (disabled && adapterReady) + m_transport->SyncRecovery(); + m_transport->RecoveryStatus(action.route, action.session, action.serial, + true, disabled ? ITransport::Recovery::ACTIVE : + ITransport::Recovery::FAILED, + disabled ? ERROR_SUCCESS : ERROR_GEN_FAILURE); + return; + } + + bool disable = false; + { + CSRWExclusiveLock lock(m_localRecoveryLock); + disable = m_recoveryActive && !m_recoveryMonitorDisabled; + if (disable) + m_recoveryMonitorDisabled = true; + } + + if (disable) + { + DEBUG_WARN( + "IDD Helper disconnected during recovery; cycling the virtual monitor"); + if (!m_monitorManager.Disable()) + { + { + CSRWExclusiveLock lock(m_localRecoveryLock); + m_recoveryMonitorDisabled = false; + } + m_transport->RecoveryStatus(action.route, action.session, action.serial, + false, ITransport::Recovery::FAILED, ERROR_GEN_FAILURE); + return; + } + } + + bool enable = false; + bool waitArrival = false; + { + CSRWExclusiveLock lock(m_localRecoveryLock); + if (m_recoveryMonitorDisabled) + { + m_localRecoveryArrivalAction = action; + m_localRecoveryArrival = true; + enable = m_adapterReady; + waitArrival = true; + } + else + m_recoveryActive = false; + } + + if (enable) + m_monitorManager.Enable(); + + if (waitArrival) + return; + + // The normal monitor is already present. With no Helper there is no + // user-session topology work to perform, so monitor presence is the local + // completion boundary. + m_transport->RecoveryStatus(action.route, action.session, action.serial, + false, ITransport::Recovery::NORMAL, ERROR_SUCCESS); +} + +void CDeviceContext::CompleteRecoveryArrival(bool arrived) +{ + RecoveryAction action; + RecoveryAction latest; + bool stale = false; + bool latestValid = false; + bool latestHandled = false; + bool latestCurrent = false; + std::unique_lock transitionLock(m_recoveryTransitionMutex); + { + CSRWExclusiveLock lock(m_localRecoveryLock); + if (!m_localRecoveryArrival) + return; + + action = m_localRecoveryArrivalAction; + const uint64_t now = GetTickCount64(); + const bool expired = action.deadline && now >= action.deadline; + const bool superseded = m_latestRecoveryValid && + !SameRecoveryAction(action, m_latestRecoveryAction); + stale = expired || superseded; + if (stale) + { + latestValid = m_latestRecoveryValid; + latest = m_latestRecoveryAction; + latestHandled = m_helperRecoveryComplete && + SameRecoveryAction(m_helperRecoveryAction, latest); + latestCurrent = latestValid && + (!latest.deadline || now < latest.deadline); + if (superseded && arrived && latestCurrent && !latest.active) + { + action = latest; + m_localRecoveryArrivalAction = latest; + stale = false; + for (auto it = m_localRecoveryQueue.begin(); + it != m_localRecoveryQueue.end();) + { + if (SameRecoveryAction(*it, latest)) + it = m_localRecoveryQueue.erase(it); + else + ++it; + } + } + else + { + m_localRecoveryArrivalAction = {}; + m_localRecoveryArrival = false; + } + } + + if (arrived && !stale) + { + // Arrival is the IDD-only NORMAL completion boundary. Keep the monitor + // present if Helper disappears while applying its user-session topology. + m_localRecoveryArrivalAction = {}; + m_localRecoveryArrival = false; + m_recoveryActive = false; + m_recoveryMonitorDisabled = false; + } + else if (!stale) + { + m_localRecoveryArrivalAction = {}; + m_localRecoveryArrival = false; + } + } + + if (stale) + { + // The expired/superseded NORMAL operation must not leave the IDD monitor + // arrived while CPipe still retains the preceding ACTIVE topology. Return + // to that stable local state before allowing newer work to proceed. + const bool disabled = m_monitorManager.Disable(); + { + CSRWExclusiveLock lock(m_localRecoveryLock); + m_recoveryActive = true; + m_recoveryMonitorDisabled = disabled; + } + + const bool queueLatest = latestValid && latest.active && + latestCurrent && !latestHandled; + transitionLock.unlock(); + if (queueLatest) + QueueLocalRecovery(latest); + return; + } + + if (!arrived) + { + const bool disabled = m_monitorManager.Disable(); + { + CSRWExclusiveLock lock(m_localRecoveryLock); + if (disabled) + { + m_recoveryActive = true; + m_recoveryMonitorDisabled = true; + } + } + transitionLock.unlock(); + m_transport->RecoveryStatus(action.route, action.session, action.serial, + false, ITransport::Recovery::FAILED, ERROR_GEN_FAILURE); + return; + } + + transitionLock.unlock(); + + // If Helper appeared while the monitor was disabled, let it restore the + // user-session topology now that the LG display path exists again. + const CPipeServer::RecoveryDispatch dispatch = g_pipe.SetRecovery( + this, action.route, action.session, action.serial, false, true); + + { + std::lock_guard completionLock(m_recoveryTransitionMutex); + CSRWExclusiveLock lock(m_localRecoveryLock); + if (m_helperRecoveryComplete && + SameRecoveryAction(m_helperRecoveryAction, action)) + return; + } + + if (dispatch == CPipeServer::RecoveryDispatch::SENT) + return; + + m_transport->RecoveryStatus(action.route, action.session, action.serial, + false, + (dispatch == CPipeServer::RecoveryDispatch::QUEUED || + dispatch == CPipeServer::RecoveryDispatch::UNAVAILABLE) ? + ITransport::Recovery::NORMAL : ITransport::Recovery::FAILED, + (dispatch == CPipeServer::RecoveryDispatch::QUEUED || + dispatch == CPipeServer::RecoveryDispatch::UNAVAILABLE) ? + ERROR_SUCCESS : RPC_S_SERVER_UNAVAILABLE); } diff --git a/idd/LGIdd/display/CDeviceContext.h b/idd/LGIdd/display/CDeviceContext.h index 6302517f..53a13e7c 100644 --- a/idd/LGIdd/display/CDeviceContext.h +++ b/idd/LGIdd/display/CDeviceContext.h @@ -26,7 +26,9 @@ #include #include +#include #include +#include #include #include @@ -55,6 +57,19 @@ private: WDFTIMER m_transportTimer = nullptr; bool m_recoveryHandlerSet = false; + std::mutex m_recoveryTransitionMutex; + CSRWLock m_localRecoveryLock; + std::deque m_localRecoveryQueue; + RecoveryAction m_localRecoveryArrivalAction = {}; + RecoveryAction m_latestRecoveryAction = {}; + RecoveryAction m_helperRecoveryAction = {}; + bool m_localRecoveryArrival = false; + bool m_latestRecoveryValid = false; + bool m_helperRecoveryComplete = false; + bool m_recoveryActive = false; + bool m_recoveryMonitorDisabled = false; + bool m_adapterReady = false; + UINT m_iddCxVersion = 0; bool m_hasIddCx110DDIs = false; bool m_canProcessFP16 = false; @@ -67,6 +82,12 @@ private: bool InitializeTransport(); void TransportTimer(); + static bool SameRecoveryAction( + const RecoveryAction& left, const RecoveryAction& right); + bool QueueLocalRecovery(const RecoveryAction& action); + void RemoveLocalRecovery(const RecoveryAction& action); + void ProcessLocalRecovery(); + void CompleteRecoveryArrival(bool arrived); InteractionResult OnSetCursorPos( const SourceKey& source, int32_t x, int32_t y) override; InteractionResult OnSetResolution(const SourceKey& source, diff --git a/idd/LGIdd/display/CMonitorManager.cpp b/idd/LGIdd/display/CMonitorManager.cpp index a47d2d9a..b1bef825 100644 --- a/idd/LGIdd/display/CMonitorManager.cpp +++ b/idd/LGIdd/display/CMonitorManager.cpp @@ -27,6 +27,7 @@ bool CMonitorManager::Create(UINT connectorIndex, IDDCX_ADAPTER adapter, std::vector edid, CDeviceContext * owner) { + std::lock_guard lifecycleLock(m_lifecycleMutex); DEBUG_INFO("Creating monitor on connector %u", connectorIndex); // We support a single monitor; never create a second one if one already @@ -34,6 +35,8 @@ bool CMonitorManager::Create(UINT connectorIndex, IDDCX_ADAPTER adapter, bool haveMonitor; { CSRWExclusiveLock lock(m_lock); + if (!m_enabled) + return false; haveMonitor = m_monitor != WDF_NO_HANDLE; } if (haveMonitor) @@ -79,18 +82,27 @@ bool CMonitorManager::Create(UINT connectorIndex, IDDCX_ADAPTER adapter, DEBUG_INFO("Monitor object created (%p)", createOut.MonitorObject); + const IDDCX_MONITOR monitor = createOut.MonitorObject; + + auto * wrapper = WdfObjectGet_CMonitorContextWrapper(monitor); + wrapper->context = new CMonitorContext(monitor, owner); + { CSRWExclusiveLock lock(m_lock); - m_monitor = createOut.MonitorObject; + m_monitor = monitor; + m_monitorDeparted = false; } - auto * wrapper = WdfObjectGet_CMonitorContextWrapper(m_monitor); - wrapper->context = new CMonitorContext(m_monitor, owner); - IDARG_OUT_MONITORARRIVAL out = {}; - status = IddCxMonitorArrival(m_monitor, &out); + status = IddCxMonitorArrival(monitor, &out); if (FAILED(status)) { + { + CSRWExclusiveLock lock(m_lock); + if (m_monitor == monitor) + m_monitor = nullptr; + } + WdfObjectDelete((WDFOBJECT)monitor); DEBUG_ERROR_HR(status, "IddCxMonitorArrival Failed"); return false; } @@ -99,12 +111,100 @@ bool CMonitorManager::Create(UINT connectorIndex, IDDCX_ADAPTER adapter, return true; } +bool CMonitorManager::Disable() +{ + std::lock_guard lifecycleLock(m_lifecycleMutex); + IDDCX_MONITOR monitor = nullptr; + bool oldReplugMonitor = false; + bool oldReplugPending = false; + bool oldMonitorDeparted = false; + bool oldWaitForSwapChainRelease = false; + LONG oldCreateQueued = 0; + LONG oldReplugQueued = 0; + { + CSRWExclusiveLock lock(m_lock); + if (!m_enabled) + return true; + + oldReplugMonitor = m_replugMonitor; + oldReplugPending = m_replugPending; + oldMonitorDeparted = m_monitorDeparted; + oldWaitForSwapChainRelease = m_waitForSwapChainRelease; + oldCreateQueued = Atomic::Load(m_createQueued); + oldReplugQueued = Atomic::Load(m_replugQueued); + + m_enabled = false; + m_replugMonitor = false; + m_replugPending = false; + Atomic::Store(m_createQueued, 0); + Atomic::Store(m_replugQueued, 0); + + monitor = m_monitor; + if (monitor == WDF_NO_HANDLE) + return true; + + m_monitor = nullptr; + m_monitorDeparted = false; + m_waitForSwapChainRelease = m_swapChainAssigned; + } + + DEBUG_INFO("Disabling the virtual monitor for recovery"); + const NTSTATUS status = IddCxMonitorDeparture(monitor); + if (!NT_SUCCESS(status)) + { + CSRWExclusiveLock lock(m_lock); + m_enabled = true; + m_monitor = monitor; + m_replugMonitor = oldReplugMonitor; + m_replugPending = oldReplugPending; + m_monitorDeparted = oldMonitorDeparted; + m_waitForSwapChainRelease = oldWaitForSwapChainRelease; + Atomic::Store(m_createQueued, oldCreateQueued); + Atomic::Store(m_replugQueued, oldReplugQueued); + DEBUG_ERROR_HR(status, + "Failed to disable the virtual monitor for recovery"); + return false; + } + + { + CSRWExclusiveLock lock(m_lock); + m_monitorDeparted = true; + } + DEBUG_INFO("Virtual monitor disabled for recovery"); + return true; +} + +void CMonitorManager::Enable() +{ + std::lock_guard lifecycleLock(m_lifecycleMutex); + bool create = false; + { + CSRWExclusiveLock lock(m_lock); + if (m_enabled) + return; + + m_enabled = true; + create = m_monitor == WDF_NO_HANDLE && + !m_waitForSwapChainRelease; + } + + if (create) + Atomic::Store(m_createQueued, 1); +} + CMonitorManager::ReplugAction CMonitorManager::Replug() { + std::lock_guard lifecycleLock(m_lifecycleMutex); IDDCX_MONITOR monitor; { CSRWExclusiveLock lock(m_lock); + if (!m_enabled) + return ReplugAction::NONE; + + if (m_waitForSwapChainRelease) + return ReplugAction::NONE; + if (m_replugMonitor || (m_swapChainAssigned && !m_swapChainReady)) { // Coalesce changes received while a swap chain is being initialized, @@ -198,10 +298,11 @@ void CMonitorManager::OnSwapChainReleased() CSRWExclusiveLock lock(m_lock); m_swapChainAssigned = false; m_swapChainReady = false; - if (m_replugMonitor && m_waitForSwapChainRelease) + if (m_waitForSwapChainRelease) { m_waitForSwapChainRelease = false; - rebuild = m_monitorDeparted; + rebuild = m_enabled && m_monitorDeparted && + m_monitor == WDF_NO_HANDLE; } } @@ -217,6 +318,9 @@ CMonitorManager::ReadyAction CMonitorManager::OnSwapChainReady() { CSRWExclusiveLock lock(m_lock); m_swapChainReady = true; + if (!m_enabled || m_waitForSwapChainRelease) + return action; + if (m_replugMonitor) { m_replugMonitor = false; @@ -250,16 +354,27 @@ CMonitorManager::ReadyAction CMonitorManager::OnSwapChainReady() void CMonitorManager::QueueReplug() { + CSRWSharedLock lock(m_lock); + if (!m_enabled) + return; Atomic::Store(m_replugQueued, 1); } CMonitorManager::DeferredAction CMonitorManager::TakeDeferredAction() { if (Atomic::Swap(m_createQueued, 0)) - return DeferredAction::CREATE; + { + CSRWSharedLock lock(m_lock); + if (m_enabled) + return DeferredAction::CREATE; + } if (Atomic::Swap(m_replugQueued, 0)) - return DeferredAction::REPLUG; + { + CSRWSharedLock lock(m_lock); + if (m_enabled) + return DeferredAction::REPLUG; + } return DeferredAction::NONE; } diff --git a/idd/LGIdd/display/CMonitorManager.h b/idd/LGIdd/display/CMonitorManager.h index d4c6b4de..a98e935f 100644 --- a/idd/LGIdd/display/CMonitorManager.h +++ b/idd/LGIdd/display/CMonitorManager.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include "config/CSettings.h" @@ -61,9 +62,14 @@ private: // IddCx callback threads, the swap-chain thread, and the transport timer. CSRWLock m_lock; + // Serializes monitor arrival/departure calls. IddCx can finish adapter + // initialization on a different thread from transport recovery work. + std::mutex m_lifecycleMutex; + bool m_replugMonitor = false; bool m_replugPending = false; bool m_monitorDeparted = false; + bool m_enabled = true; bool m_swapChainAssigned = false; bool m_swapChainReady = false; bool m_waitForSwapChainRelease = false; @@ -77,6 +83,8 @@ private: public: bool Create(UINT connectorIndex, IDDCX_ADAPTER adapter, std::vector edid, CDeviceContext * owner); + bool Disable(); + void Enable(); ReplugAction Replug(); void RequestMode(const CSettings::DisplayMode& mode); diff --git a/idd/LGIdd/ipc/CPipeServer.cpp b/idd/LGIdd/ipc/CPipeServer.cpp index 268579d1..3cd8cd86 100644 --- a/idd/LGIdd/ipc/CPipeServer.cpp +++ b/idd/LGIdd/ipc/CPipeServer.cpp @@ -369,7 +369,8 @@ void CPipeServer::OnPipeConnected() // latest request whenever the helper reconnects so a helper restart cannot // silently restore the IDD-only topology while recovery is active. if (m_recoveryValid) - m_endpoint.Send(&m_recoveryRequest, sizeof(m_recoveryRequest)); + m_recoveryPending = m_endpoint.Send( + &m_recoveryRequest, sizeof(m_recoveryRequest)); } void CPipeServer::OnPipeDisconnected() @@ -384,6 +385,37 @@ void CPipeServer::OnPipeDisconnected() m_clientAuthorityId[0] = 0; m_clientAuthorityId[1] = 0; } + + CSRWExclusiveLock queueLock(m_queueLock); + CSRWSharedLock recoveryLock(m_recoveryLock); + if (m_recoveryValid && + (m_recoveryPending || m_recoveryHelperActive) && + m_recoveryHandler) + { + const uint64_t route = m_recoveryRoute; + const uint64_t session = m_recoveryRequest.recovery.session; + const uint32_t serial = m_recoveryRequest.recovery.request & + ~LGPipeMsg::RECOVERY_ACTIVE; + const bool active = (m_recoveryRequest.recovery.request & + LGPipeMsg::RECOVERY_ACTIVE) != 0; + + // If NORMAL was sent while Helper still owned the recovery topology, + // do not replay it after a disconnect until the IDD monitor is present. + // Preserve the preceding ACTIVE command for reconnect in the meantime. + if (!active && !m_recoveryReplaySafe && m_recoveryActiveValid) + { + m_recoveryValid = true; + m_recoveryRoute = m_recoveryActiveRoute; + m_recoveryRequest = m_recoveryActiveRequest; + m_recoveryReplaySafe = true; + } + + m_recoveryPending = false; + m_recoveryHelperActive = false; + queueLock.Unlock(); + m_recoveryHandler(m_recoveryOpaque, route, session, serial, active, + RecoveryResult::HELPER_UNAVAILABLE); + } } bool CPipeServer::OnPipeMessage(const void * message, size_t size) @@ -494,7 +526,7 @@ void CPipeServer::HandleReloadSettings() void CPipeServer::HandleRecovery(const LGPipeMsg & msg) { - CSRWSharedLock queueLock(m_queueLock); + CSRWExclusiveLock queueLock(m_queueLock); if (!m_recoveryValid || msg.recovery.session != m_recoveryRequest.recovery.session || msg.recovery.request != m_recoveryRequest.recovery.request) @@ -505,14 +537,44 @@ void CPipeServer::HandleRecovery(const LGPipeMsg & msg) const uint32_t serial = msg.recovery.request & ~LGPipeMsg::RECOVERY_ACTIVE; - const bool active = + const bool active = (msg.recovery.request & LGPipeMsg::RECOVERY_ACTIVE) != 0; + const uint64_t route = m_recoveryRoute; + RecoveryResult result; + switch (msg.type) + { + case LGPipeMsg::RECOVERY_OFF: + result = RecoveryResult::NORMAL; + m_recoveryHelperActive = false; + m_recoveryReplaySafe = true; + m_recoveryActiveValid = false; + m_recoveryActiveRoute = 0; + m_recoveryActiveRequest = {}; + break; + + case LGPipeMsg::RECOVERY_ON: + result = RecoveryResult::ACTIVE; + m_recoveryHelperActive = true; + break; + + case LGPipeMsg::RECOVERY_FAILED: + result = RecoveryResult::FAILED; + break; + + case LGPipeMsg::RECOVERY_NO_DISPLAY: + result = RecoveryResult::NO_DISPLAY; + break; + + default: + return; + } + m_recoveryPending = false; CSRWSharedLock recoveryLock(m_recoveryLock); queueLock.Unlock(); if (m_recoveryHandler) m_recoveryHandler(m_recoveryOpaque, - m_recoveryRoute, msg.recovery.session, serial, active, msg.type); + route, msg.recovery.session, serial, active, result); } void CPipeServer::SetDeviceContext(CDeviceContext * context) @@ -526,11 +588,19 @@ void CPipeServer::SetRecoveryHandler( { CSRWExclusiveLock queueLock(m_queueLock); CSRWExclusiveLock recoveryLock(m_recoveryLock); - m_recoveryRoute = 0; - m_recoveryValid = false; - m_recoveryRequest = {}; - m_recoveryHandler = handler; - m_recoveryOpaque = opaque; + m_recoveryRoute = 0; + m_recoveryValid = false; + m_recoveryPending = false; + m_recoveryHelperActive = false; + m_recoveryReplaySafe = true; + m_recoveryActiveValid = false; + m_recoveryRequest = {}; + m_recoveryActiveRequest = {}; + m_recoveryNewestRequest = {}; + m_recoveryActiveRoute = 0; + m_recoveryNewestRoute = 0; + m_recoveryHandler = handler; + m_recoveryOpaque = opaque; } void CPipeServer::ClearRecoveryHandler(void * opaque) @@ -540,11 +610,19 @@ void CPipeServer::ClearRecoveryHandler(void * opaque) if (m_recoveryOpaque != opaque) return; - m_recoveryRoute = 0; - m_recoveryValid = false; - m_recoveryRequest = {}; - m_recoveryHandler = nullptr; - m_recoveryOpaque = nullptr; + m_recoveryRoute = 0; + m_recoveryValid = false; + m_recoveryPending = false; + m_recoveryHelperActive = false; + m_recoveryReplaySafe = true; + m_recoveryActiveValid = false; + m_recoveryRequest = {}; + m_recoveryActiveRequest = {}; + m_recoveryNewestRequest = {}; + m_recoveryActiveRoute = 0; + m_recoveryNewestRoute = 0; + m_recoveryHandler = nullptr; + m_recoveryOpaque = nullptr; } bool CPipeServer::SetCursorPos(int32_t x, int32_t y) @@ -596,14 +674,16 @@ void CPipeServer::ResolutionRejected(uint32_t width, uint32_t height, WriteMsg(msg); } -bool CPipeServer::SetRecovery(void * owner, uint64_t route, - uint64_t session, uint32_t serial, bool active) +CPipeServer::RecoveryDispatch CPipeServer::SetRecovery( + void * owner, uint64_t route, + uint64_t session, uint32_t serial, bool active, + bool replayIfUnavailable) { if (!route || !session || !serial || (serial & LGPipeMsg::RECOVERY_ACTIVE)) { DEBUG_ERROR("Invalid recovery request correlation"); - return false; + return RecoveryDispatch::REJECTED; } LGPipeMsg msg = {}; @@ -616,11 +696,57 @@ bool CPipeServer::SetRecovery(void * owner, uint64_t route, CSRWExclusiveLock queueLock(m_queueLock); CSRWExclusiveLock recoveryLock(m_recoveryLock); if (!m_recoveryHandler || m_recoveryOpaque != owner) - return false; + return RecoveryDispatch::REJECTED; - m_recoveryValid = true; - m_recoveryRoute = route; - m_recoveryRequest = msg; - m_endpoint.Send(&msg, sizeof(msg)); - return true; + // RecoveryHub routes increase for the lifetime of this handler. Keep a + // watermark separate from the replay request so restoring retained ACTIVE + // state cannot let a late, older NORMAL overwrite newer work. + if (m_recoveryNewestRoute) + { + if (route < m_recoveryNewestRoute) + return RecoveryDispatch::REJECTED; + if (route == m_recoveryNewestRoute && + (msg.recovery.session != + m_recoveryNewestRequest.recovery.session || + msg.recovery.request != + m_recoveryNewestRequest.recovery.request)) + return RecoveryDispatch::REJECTED; + } + if (route > m_recoveryNewestRoute) + { + m_recoveryNewestRoute = route; + m_recoveryNewestRequest = msg; + } + + const bool previousValid = m_recoveryValid; + const bool previousPending = m_recoveryPending; + const bool previousReplaySafe = m_recoveryReplaySafe; + const uint64_t previousRoute = m_recoveryRoute; + const LGPipeMsg previousRequest = m_recoveryRequest; + + m_recoveryValid = true; + m_recoveryRoute = route; + m_recoveryRequest = msg; + m_recoveryReplaySafe = active || replayIfUnavailable; + if (active) + { + m_recoveryActiveValid = true; + m_recoveryActiveRoute = route; + m_recoveryActiveRequest = msg; + } + m_recoveryPending = m_endpoint.Send(&msg, sizeof(msg)); + if (m_recoveryPending) + return RecoveryDispatch::SENT; + + if (replayIfUnavailable) + return RecoveryDispatch::QUEUED; + + // A NORMAL request cannot be replayed while the IDD monitor is locally + // absent. Retain the prior ACTIVE request until the monitor has re-arrived. + m_recoveryValid = previousValid; + m_recoveryPending = previousPending; + m_recoveryReplaySafe = previousReplaySafe; + m_recoveryRoute = previousRoute; + m_recoveryRequest = previousRequest; + return RecoveryDispatch::UNAVAILABLE; } diff --git a/idd/LGIdd/ipc/CPipeServer.h b/idd/LGIdd/ipc/CPipeServer.h index 16ab04e5..8d1613e3 100644 --- a/idd/LGIdd/ipc/CPipeServer.h +++ b/idd/LGIdd/ipc/CPipeServer.h @@ -44,17 +44,42 @@ class CPipeServer : private IPipeEndpointHandler, public IClipboardChannelDoorbell { public: + enum class RecoveryResult + { + NORMAL, + ACTIVE, + FAILED, + NO_DISPLAY, + HELPER_UNAVAILABLE, + }; + + enum class RecoveryDispatch + { + REJECTED, + UNAVAILABLE, + QUEUED, + SENT, + }; + using RecoveryHandler = void (*)(void * opaque, uint64_t route, uint64_t session, uint32_t serial, bool active, - LGPipeMsg::Type result); + RecoveryResult result); private: CPipeEndpoint m_endpoint; CClipboardChannel m_clipboard; CSRWLock m_queueLock; std::vector m_queue; - bool m_recoveryValid = false; - LGPipeMsg m_recoveryRequest = {}; + bool m_recoveryValid = false; + bool m_recoveryPending = false; + bool m_recoveryHelperActive = false; + bool m_recoveryReplaySafe = true; + bool m_recoveryActiveValid = false; + LGPipeMsg m_recoveryRequest = {}; + LGPipeMsg m_recoveryActiveRequest = {}; + LGPipeMsg m_recoveryNewestRequest = {}; + uint64_t m_recoveryActiveRoute = 0; + uint64_t m_recoveryNewestRoute = 0; CSRWLock m_deviceContextLock; CDeviceContext * m_deviceContext = nullptr; @@ -115,8 +140,9 @@ class CPipeServer : private IPipeEndpointHandler, void SetGPUStatus(bool software); void ResolutionRejected(uint32_t width, uint32_t height, uint32_t requiredSizeMiB); - bool SetRecovery(void * owner, uint64_t route, - uint64_t session, uint32_t serial, bool active); + RecoveryDispatch SetRecovery(void * owner, uint64_t route, + uint64_t session, uint32_t serial, bool active, + bool replayIfUnavailable = true); CClipboardChannel& Clipboard() { return m_clipboard; } bool ClipboardKick(uint64_t epoch) override;