mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 07:01:30 +00:00
[idd] recovery: restore fallback display
Add a protocol-independent recovery channel outside LGMP so clients can request a usable guest display even when transport versions differ. Synchronize recovery state with the helper across driver restarts and restore the configured topology without persisting temporary changes.
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
|
||||
#include "CDebug.h"
|
||||
#include "common/KVMFR.h"
|
||||
#include "common/KVMFRRecovery.h"
|
||||
|
||||
static bool TranslateFrameScheduleFlags(
|
||||
uint32_t source, uint32_t& destination)
|
||||
@@ -67,6 +68,9 @@ ITransport::OpenResult CLGMPTransport::Open()
|
||||
|
||||
bool CLGMPTransport::Initialize()
|
||||
{
|
||||
if (!m_recovery.Initialize(m_ivshmem))
|
||||
return false;
|
||||
|
||||
if (!m_host.Initialize(m_ivshmem))
|
||||
return false;
|
||||
|
||||
@@ -82,11 +86,26 @@ bool CLGMPTransport::Initialize()
|
||||
|
||||
bool CLGMPTransport::Setup(size_t alignment)
|
||||
{
|
||||
return m_frames.Setup(alignment);
|
||||
if (!m_frames.Setup(alignment))
|
||||
return false;
|
||||
|
||||
m_ready.store(true, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CLGMPTransport::Process(ITransportEvents& events)
|
||||
{
|
||||
const CRecovery::Request recovery = m_recovery.Process();
|
||||
if (recovery.valid)
|
||||
events.OnRecoveryRequest(
|
||||
recovery.session, recovery.serial, recovery.active);
|
||||
|
||||
// Before the swap chain establishes the frame-buffer alignment, service
|
||||
// only the protocol-independent recovery channel. This preserves the old
|
||||
// transport startup boundary while keeping recovery available immediately.
|
||||
if (!m_ready.load(std::memory_order_acquire))
|
||||
return;
|
||||
|
||||
const LGMP_STATUS processStatus = m_host.Process();
|
||||
if (processStatus != LGMP_OK)
|
||||
{
|
||||
@@ -174,6 +193,39 @@ void CLGMPTransport::Process(ITransportEvents& events)
|
||||
m_control.ResendState();
|
||||
}
|
||||
|
||||
void CLGMPTransport::SyncRecovery()
|
||||
{
|
||||
m_recovery.Sync();
|
||||
}
|
||||
|
||||
void CLGMPTransport::RecoveryStatus(
|
||||
uint64_t session, uint32_t serial, bool active,
|
||||
Recovery state, uint32_t error)
|
||||
{
|
||||
uint32_t wireState = KVMFR_R_STATE_FAILED;
|
||||
uint32_t wireError = KVMFR_R_ERR_NONE;
|
||||
switch (state)
|
||||
{
|
||||
case Recovery::NORMAL:
|
||||
wireState = KVMFR_R_STATE_NORMAL;
|
||||
break;
|
||||
|
||||
case Recovery::ACTIVE:
|
||||
wireState = KVMFR_R_STATE_ACTIVE;
|
||||
break;
|
||||
|
||||
case Recovery::FAILED:
|
||||
wireState = KVMFR_R_STATE_FAILED;
|
||||
wireError = error == ERROR_NOT_FOUND ?
|
||||
KVMFR_R_ERR_NO_FALLBACK_DISPLAY :
|
||||
KVMFR_R_ERR_TOPOLOGY_FAILED;
|
||||
break;
|
||||
}
|
||||
|
||||
m_recovery.SetStatus(
|
||||
session, serial, active, wireState, wireError);
|
||||
}
|
||||
|
||||
FrameMemoryLimits CLGMPTransport::GetMemoryLimits() const
|
||||
{
|
||||
return m_frames.GetMemoryLimits();
|
||||
|
||||
@@ -26,6 +26,9 @@
|
||||
#include "transport/lgmp/CLGMPFrameTransport.h"
|
||||
#include "transport/lgmp/CLGMPHost.h"
|
||||
#include "transport/lgmp/CLGMPInputTransport.h"
|
||||
#include "transport/lgmp/CRecovery.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
class CLGMPTransport final : public ITransport
|
||||
{
|
||||
@@ -37,6 +40,8 @@ private:
|
||||
CLGMPControl m_control;
|
||||
CLGMPFrameTransport m_frames;
|
||||
CLGMPInputTransport m_input;
|
||||
CRecovery m_recovery;
|
||||
std::atomic<bool> m_ready = false;
|
||||
|
||||
public:
|
||||
CLGMPTransport();
|
||||
@@ -49,6 +54,10 @@ public:
|
||||
bool Initialize() override;
|
||||
bool Setup(size_t alignment) override;
|
||||
void Process(ITransportEvents& events) override;
|
||||
void SyncRecovery() override;
|
||||
void RecoveryStatus(
|
||||
uint64_t session, uint32_t serial, bool active,
|
||||
Recovery state, uint32_t error) override;
|
||||
|
||||
FrameMemoryLimits GetMemoryLimits() const override;
|
||||
DirectFrameBufferMemory GetDirectMemory() const override;
|
||||
|
||||
408
idd/LGIdd/transport/lgmp/CRecovery.cpp
Normal file
408
idd/LGIdd/transport/lgmp/CRecovery.cpp
Normal file
@@ -0,0 +1,408 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2026 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "transport/lgmp/CRecovery.h"
|
||||
|
||||
#include "transport/lgmp/CIVSHMEM.h"
|
||||
#include "platform/CPlatformInfo.h"
|
||||
#include "CDebug.h"
|
||||
#include "VersionInfo.h"
|
||||
|
||||
#include "common/KVMFR.h"
|
||||
#include "common/KVMFRRecovery.h"
|
||||
|
||||
#include <lgmp/lgmp.h>
|
||||
|
||||
#include <Windows.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
static const uint64_t HELPER_TIMEOUT_MS = 30000;
|
||||
CSRWLock l_wireLock;
|
||||
|
||||
uint32_t AtomicRead(uint32_t& value)
|
||||
{
|
||||
return static_cast<uint32_t>(InterlockedCompareExchange(
|
||||
(volatile LONG *)&value, 0, 0));
|
||||
}
|
||||
|
||||
void AtomicWrite(uint32_t& value, uint32_t data)
|
||||
{
|
||||
InterlockedExchange((volatile LONG *)&value, static_cast<LONG>(data));
|
||||
}
|
||||
|
||||
void AtomicIncrement(uint32_t& value)
|
||||
{
|
||||
InterlockedIncrement((volatile LONG *)&value);
|
||||
}
|
||||
|
||||
uint32_t AtomicAdd(uint32_t& value, uint32_t data)
|
||||
{
|
||||
return static_cast<uint32_t>(InterlockedExchangeAdd(
|
||||
(volatile LONG *)&value, static_cast<LONG>(data))) + data;
|
||||
}
|
||||
|
||||
bool AtomicCompareExchange(
|
||||
uint32_t& value, uint32_t expected, uint32_t data)
|
||||
{
|
||||
return static_cast<uint32_t>(InterlockedCompareExchange(
|
||||
(volatile LONG *)&value, static_cast<LONG>(data),
|
||||
static_cast<LONG>(expected))) == expected;
|
||||
}
|
||||
|
||||
uint64_t CreateSession(const void * memory, uint64_t previous)
|
||||
{
|
||||
LARGE_INTEGER counter;
|
||||
QueryPerformanceCounter(&counter);
|
||||
|
||||
uint64_t session = static_cast<uint64_t>(counter.QuadPart) ^
|
||||
(GetTickCount64() << 24) ^
|
||||
static_cast<uint64_t>(reinterpret_cast<uintptr_t>(memory)) ^
|
||||
(static_cast<uint64_t>(GetCurrentProcessId()) << 32) ^
|
||||
GetCurrentThreadId();
|
||||
|
||||
if (!session || session == previous)
|
||||
++session;
|
||||
if (!session)
|
||||
++session;
|
||||
return session;
|
||||
}
|
||||
}
|
||||
|
||||
bool CRecovery::OwnsSession()
|
||||
{
|
||||
if (AtomicRead(m_data->header.ready) != KVMFR_R_READY)
|
||||
return false;
|
||||
|
||||
const uint64_t session = m_data->header.session;
|
||||
MemoryBarrier();
|
||||
return session == m_session &&
|
||||
AtomicRead(m_data->header.ready) == KVMFR_R_READY;
|
||||
}
|
||||
|
||||
bool CRecovery::ReadRequest(
|
||||
KVMFRRRequest& source, KVMFRRRequest& result)
|
||||
{
|
||||
for (unsigned i = 0; i < 4; ++i)
|
||||
{
|
||||
const uint32_t serial = AtomicRead(source.serial);
|
||||
if (!serial || (serial & 1U))
|
||||
return false;
|
||||
|
||||
const uint32_t type = source.request;
|
||||
const uint64_t session = source.session;
|
||||
MemoryBarrier();
|
||||
if (AtomicRead(source.serial) == serial)
|
||||
{
|
||||
result.serial = serial;
|
||||
result.request = type;
|
||||
result.session = session;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CRecovery::ReadStatus(KVMFRRStatus& source, KVMFRRStatus& result)
|
||||
{
|
||||
for (unsigned i = 0; i < 4; ++i)
|
||||
{
|
||||
const uint32_t serial = AtomicRead(source.serial);
|
||||
if (!serial || (serial & 1U))
|
||||
continue;
|
||||
|
||||
result.ackSerial = source.ackSerial;
|
||||
result.ackRequest = source.ackRequest;
|
||||
result.state = source.state;
|
||||
result.error = source.error;
|
||||
result.session = source.session;
|
||||
MemoryBarrier();
|
||||
if (AtomicRead(source.serial) == serial)
|
||||
{
|
||||
result.serial = serial;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CRecovery::SerialNewer(uint32_t serial, uint32_t reference)
|
||||
{
|
||||
const uint32_t difference = serial - reference;
|
||||
return difference && difference < 0x80000000U;
|
||||
}
|
||||
|
||||
uint32_t CRecovery::NextTicket()
|
||||
{
|
||||
uint32_t ticket = AtomicAdd(m_data->req.ticket, 2U);
|
||||
if (!ticket)
|
||||
ticket = AtomicAdd(m_data->req.ticket, 2U);
|
||||
return ticket;
|
||||
}
|
||||
|
||||
void CRecovery::Publish(uint32_t serial, uint32_t request,
|
||||
uint32_t state, uint32_t error)
|
||||
{
|
||||
const uint32_t writing = m_statusSerial | 1U;
|
||||
uint32_t published = writing + 1U;
|
||||
if (!published)
|
||||
published = KVMFR_R_REQ_FIRST;
|
||||
|
||||
AtomicWrite(m_data->status.serial, writing);
|
||||
m_data->status.ackRequest = request;
|
||||
m_data->status.state = state;
|
||||
m_data->status.error = error;
|
||||
m_data->status.session = m_session;
|
||||
m_data->status.ackSerial = serial;
|
||||
AtomicWrite(m_data->status.serial, published);
|
||||
m_statusSerial = published;
|
||||
}
|
||||
|
||||
bool CRecovery::Initialize(CIVSHMEM& ivshmem)
|
||||
{
|
||||
CSRWExclusiveLock wireLock(l_wireLock);
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (m_data)
|
||||
return OwnsSession();
|
||||
|
||||
m_data = static_cast<KVMFRR *>(ivshmem.GetRecoveryMem());
|
||||
if (!m_data)
|
||||
{
|
||||
DEBUG_ERROR("IVSHMEM is too small for the recovery region");
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t oldReady = AtomicRead(m_data->header.ready);
|
||||
const bool oldValid = oldReady == KVMFR_R_READY &&
|
||||
memcmp(m_data->header.magic, KVMFR_R_MAGIC,
|
||||
sizeof(m_data->header.magic)) == 0 &&
|
||||
m_data->header.abiVersion == KVMFR_R_VERSION &&
|
||||
m_data->header.structSize >= sizeof(KVMFRR) &&
|
||||
m_data->header.session != 0;
|
||||
const uint64_t oldSession = oldValid ? m_data->header.session : 0;
|
||||
|
||||
uint32_t retainedRequest = KVMFR_R_REQ_NONE;
|
||||
if (oldValid)
|
||||
{
|
||||
KVMFRRStatus status = {};
|
||||
if (ReadStatus(m_data->status, status) &&
|
||||
status.session == oldSession)
|
||||
{
|
||||
if (status.ackRequest == KVMFR_R_REQ_RECOVERY &&
|
||||
(status.state == KVMFR_R_STATE_SWITCHING ||
|
||||
status.state == KVMFR_R_STATE_ACTIVE ||
|
||||
status.state == KVMFR_R_STATE_FAILED))
|
||||
retainedRequest = KVMFR_R_REQ_RECOVERY;
|
||||
else if (status.ackRequest == KVMFR_R_REQ_NORMAL &&
|
||||
(status.state == KVMFR_R_STATE_SWITCHING ||
|
||||
status.state == KVMFR_R_STATE_FAILED))
|
||||
retainedRequest = KVMFR_R_REQ_NORMAL;
|
||||
}
|
||||
}
|
||||
|
||||
AtomicWrite(m_data->header.ready, 0);
|
||||
if (oldValid)
|
||||
{
|
||||
ZeroMemory(&m_data->header, sizeof(m_data->header));
|
||||
ZeroMemory(&m_data->info, sizeof(m_data->info));
|
||||
ZeroMemory(&m_data->status, sizeof(m_data->status));
|
||||
|
||||
// Preserve the client-owned ticket and interrupted odd slots. Completed
|
||||
// requests belong to the old producer session and can now be reclaimed.
|
||||
for (unsigned i = 0; i < KVMFR_R_REQ_SLOTS; ++i)
|
||||
{
|
||||
KVMFRRRequest request = {};
|
||||
if (ReadRequest(m_data->requests[i], request))
|
||||
AtomicCompareExchange(
|
||||
m_data->requests[i].serial, request.serial, 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
ZeroMemory(m_data, sizeof(*m_data));
|
||||
|
||||
m_session = CreateSession(m_data, oldSession);
|
||||
|
||||
memcpy(m_data->header.magic, KVMFR_R_MAGIC,
|
||||
sizeof(m_data->header.magic));
|
||||
m_data->header.abiVersion = KVMFR_R_VERSION;
|
||||
m_data->header.structSize = static_cast<uint16_t>(sizeof(*m_data));
|
||||
m_data->header.capabilities = KVMFR_R_CAP_DISPLAY;
|
||||
m_data->header.lgmpVersion = LGMP_PROTOCOL_VERSION;
|
||||
m_data->header.kvmfrVersion = KVMFR_VERSION;
|
||||
m_data->header.session = m_session;
|
||||
memcpy(m_data->header.uuid, CPlatformInfo::GetUUID(),
|
||||
sizeof(m_data->header.uuid));
|
||||
m_data->header.heartbeat = 1;
|
||||
strncpy_s(m_data->info.version, sizeof(m_data->info.version),
|
||||
LG_VERSION_STR, _TRUNCATE);
|
||||
|
||||
m_request = retainedRequest == KVMFR_R_REQ_NONE ?
|
||||
KVMFR_R_REQ_NORMAL : retainedRequest;
|
||||
m_lastSerial = NextTicket();
|
||||
m_replay = true;
|
||||
Publish(m_lastSerial, m_request,
|
||||
KVMFR_R_STATE_SWITCHING, KVMFR_R_ERR_NONE);
|
||||
|
||||
m_nextHeartbeat = GetTickCount64() + KVMFR_R_HEARTBEAT_MS;
|
||||
AtomicWrite(m_data->header.ready, KVMFR_R_READY);
|
||||
|
||||
DEBUG_INFO("Recovery channel initialized (session %llu%s)",
|
||||
(unsigned long long)m_session,
|
||||
retainedRequest != KVMFR_R_REQ_NONE ? ", request retained" : "");
|
||||
return true;
|
||||
}
|
||||
|
||||
void CRecovery::Sync()
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
m_syncReady = true;
|
||||
}
|
||||
|
||||
CRecovery::Request CRecovery::Process()
|
||||
{
|
||||
Request result;
|
||||
CSRWExclusiveLock wireLock(l_wireLock);
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (!m_data || !OwnsSession())
|
||||
return result;
|
||||
|
||||
const uint64_t now = GetTickCount64();
|
||||
if (now >= m_nextHeartbeat)
|
||||
{
|
||||
AtomicIncrement(m_data->header.heartbeat);
|
||||
m_nextHeartbeat = now + KVMFR_R_HEARTBEAT_MS;
|
||||
}
|
||||
|
||||
KVMFRRRequest requests[KVMFR_R_REQ_SLOTS] = {};
|
||||
bool stable[KVMFR_R_REQ_SLOTS] = {};
|
||||
KVMFRRRequest request = {};
|
||||
bool haveRequest = false;
|
||||
for (unsigned i = 0; i < KVMFR_R_REQ_SLOTS; ++i)
|
||||
{
|
||||
stable[i] = ReadRequest(m_data->requests[i], requests[i]);
|
||||
if (!stable[i] || requests[i].session != m_session ||
|
||||
!SerialNewer(requests[i].serial, m_lastSerial))
|
||||
continue;
|
||||
|
||||
if (!haveRequest || SerialNewer(requests[i].serial, request.serial))
|
||||
{
|
||||
request = requests[i];
|
||||
haveRequest = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (haveRequest)
|
||||
{
|
||||
m_lastSerial = request.serial;
|
||||
|
||||
if (request.session == m_session &&
|
||||
(request.request == KVMFR_R_REQ_NORMAL ||
|
||||
request.request == KVMFR_R_REQ_RECOVERY))
|
||||
{
|
||||
m_request = request.request;
|
||||
m_replay = m_request == KVMFR_R_REQ_NORMAL && !m_syncReady;
|
||||
m_waiting = false;
|
||||
Publish(m_lastSerial, m_request,
|
||||
KVMFR_R_STATE_SWITCHING, KVMFR_R_ERR_NONE);
|
||||
|
||||
if (m_replay)
|
||||
DEBUG_INFO("Deferring recovery request %u until monitor arrival",
|
||||
m_lastSerial);
|
||||
else
|
||||
{
|
||||
m_waiting = true;
|
||||
m_deadline = now + HELPER_TIMEOUT_MS;
|
||||
|
||||
result.session = m_session;
|
||||
result.serial = m_lastSerial;
|
||||
result.valid = true;
|
||||
result.active = m_request == KVMFR_R_REQ_RECOVERY;
|
||||
DEBUG_INFO("Recovery mode request %u: %s", m_lastSerial,
|
||||
result.active ? "active" : "normal");
|
||||
}
|
||||
}
|
||||
else if (request.session == m_session)
|
||||
{
|
||||
m_request = request.request;
|
||||
m_replay = false;
|
||||
m_waiting = false;
|
||||
Publish(m_lastSerial, m_request,
|
||||
KVMFR_R_STATE_FAILED, KVMFR_R_ERR_UNSUPPORTED);
|
||||
DEBUG_WARN("Ignoring invalid recovery request %u", m_lastSerial);
|
||||
}
|
||||
}
|
||||
else if (m_replay &&
|
||||
(m_request != KVMFR_R_REQ_NORMAL || m_syncReady))
|
||||
{
|
||||
m_replay = false;
|
||||
m_waiting = true;
|
||||
m_deadline = now + HELPER_TIMEOUT_MS;
|
||||
|
||||
result.session = m_session;
|
||||
result.serial = m_lastSerial;
|
||||
result.valid = true;
|
||||
result.active = m_request == KVMFR_R_REQ_RECOVERY;
|
||||
DEBUG_INFO("Synchronizing recovery helper mode: %s",
|
||||
result.active ? "active" : "normal");
|
||||
}
|
||||
|
||||
// A stable slot cannot be reused until the producer clears it. Publish the
|
||||
// selected request's state first so its client cannot observe a reclaimed
|
||||
// slot without a corresponding acknowledgement.
|
||||
for (unsigned i = 0; i < KVMFR_R_REQ_SLOTS; ++i)
|
||||
if (stable[i])
|
||||
AtomicCompareExchange(
|
||||
m_data->requests[i].serial, requests[i].serial, 0);
|
||||
|
||||
if (m_waiting && now >= m_deadline)
|
||||
{
|
||||
m_waiting = false;
|
||||
Publish(m_lastSerial, m_request,
|
||||
KVMFR_R_STATE_FAILED, KVMFR_R_ERR_HELPER_UNAVAILABLE);
|
||||
DEBUG_WARN("Recovery helper did not respond to request %u",
|
||||
m_lastSerial);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void CRecovery::SetStatus(uint64_t session, uint32_t serial, bool active,
|
||||
uint32_t state, uint32_t error)
|
||||
{
|
||||
CSRWExclusiveLock wireLock(l_wireLock);
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
const bool expectedActive = m_request == KVMFR_R_REQ_RECOVERY;
|
||||
if (!m_data || !OwnsSession() || session != m_session ||
|
||||
serial != m_lastSerial ||
|
||||
active != expectedActive ||
|
||||
(state == KVMFR_R_STATE_ACTIVE && !active) ||
|
||||
(state == KVMFR_R_STATE_NORMAL && active))
|
||||
{
|
||||
DEBUG_WARN("Ignoring stale recovery helper status");
|
||||
return;
|
||||
}
|
||||
|
||||
m_waiting = false;
|
||||
Publish(serial, m_request, state, error);
|
||||
DEBUG_INFO("Recovery request %u completed with state %u", serial, state);
|
||||
}
|
||||
72
idd/LGIdd/transport/lgmp/CRecovery.h
Normal file
72
idd/LGIdd/transport/lgmp/CRecovery.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2026 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CSRWLock.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct KVMFRR;
|
||||
struct KVMFRRRequest;
|
||||
struct KVMFRRStatus;
|
||||
|
||||
class CIVSHMEM;
|
||||
|
||||
class CRecovery
|
||||
{
|
||||
public:
|
||||
struct Request
|
||||
{
|
||||
uint64_t session = 0;
|
||||
uint32_t serial = 0;
|
||||
bool valid = false;
|
||||
bool active = false;
|
||||
};
|
||||
|
||||
private:
|
||||
CSRWLock m_lock;
|
||||
KVMFRR * m_data = nullptr;
|
||||
|
||||
uint64_t m_session = 0;
|
||||
uint64_t m_nextHeartbeat = 0;
|
||||
uint64_t m_deadline = 0;
|
||||
uint32_t m_lastSerial = 0;
|
||||
uint32_t m_statusSerial = 0;
|
||||
uint32_t m_request = 0;
|
||||
bool m_syncReady = false;
|
||||
bool m_replay = false;
|
||||
bool m_waiting = false;
|
||||
|
||||
static bool ReadRequest(KVMFRRRequest& source, KVMFRRRequest& result);
|
||||
static bool ReadStatus(KVMFRRStatus& source, KVMFRRStatus& result);
|
||||
static bool SerialNewer(uint32_t serial, uint32_t reference);
|
||||
bool OwnsSession();
|
||||
uint32_t NextTicket();
|
||||
void Publish(uint32_t serial, uint32_t request,
|
||||
uint32_t state, uint32_t error);
|
||||
|
||||
public:
|
||||
bool Initialize(CIVSHMEM& ivshmem);
|
||||
void Sync();
|
||||
Request Process();
|
||||
void SetStatus(uint64_t session, uint32_t serial, bool active,
|
||||
uint32_t state, uint32_t error);
|
||||
};
|
||||
Reference in New Issue
Block a user