[idd] recovery: unplug monitor without helper

When LGIddHelper is unavailable, recovery requests could remain
pending until timeout while the virtual monitor stayed connected.
This could leave a logged-out guest without a usable display.

Fall back to departing the IDD monitor for active recovery, then
re-arrive it before normal recovery completes. Serialize monitor
lifecycle changes and preserve recovery ordering across Helper
reconnects, timeouts, and stale responses.
This commit is contained in:
Geoffrey McRae
2026-08-20 16:03:58 +10:00
parent 7b059ad67e
commit 60a51fccdf
6 changed files with 777 additions and 47 deletions

View File

@@ -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<CDeviceContext *>(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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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);
}

View File

@@ -26,7 +26,9 @@
#include <wdf.h>
#include <IddCx.h>
#include <deque>
#include <memory>
#include <mutex>
#include <stddef.h>
#include <stdint.h>
@@ -55,6 +57,19 @@ private:
WDFTIMER m_transportTimer = nullptr;
bool m_recoveryHandlerSet = false;
std::mutex m_recoveryTransitionMutex;
CSRWLock m_localRecoveryLock;
std::deque<RecoveryAction> 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,

View File

@@ -27,6 +27,7 @@
bool CMonitorManager::Create(UINT connectorIndex, IDDCX_ADAPTER adapter,
std::vector<BYTE> edid, CDeviceContext * owner)
{
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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;
}

View File

@@ -25,6 +25,7 @@
#include <Windows.h>
#include <wdf.h>
#include <IddCx.h>
#include <mutex>
#include <vector>
#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<BYTE> edid, CDeviceContext * owner);
bool Disable();
void Enable();
ReplugAction Replug();
void RequestMode(const CSettings::DisplayMode& mode);

View File

@@ -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;
}

View File

@@ -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<LGPipeMsg> 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;