mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 07:01:30 +00:00
[client] transport: recover incompatible sessions
Report expected and current protocol versions through the transport interface and expose session-independent recovery operations. Offer recovery from the version mismatch popup, publish requests through the reserved IVSHMEM channel, and select the validated SPICE fallback.
This commit is contained in:
@@ -182,6 +182,10 @@ void app_alert(LG_MsgAlert type, const char * fmt, ...);
|
|||||||
typedef struct MsgBoxHandle * MsgBoxHandle;
|
typedef struct MsgBoxHandle * MsgBoxHandle;
|
||||||
MsgBoxHandle app_msgBox(const char * caption, const char * fmt, ...);
|
MsgBoxHandle app_msgBox(const char * caption, const char * fmt, ...);
|
||||||
|
|
||||||
|
typedef void (*MsgBoxCloseCallback)(MsgBoxHandle handle, void * opaque);
|
||||||
|
MsgBoxHandle app_msgBoxWithClose(const char * caption,
|
||||||
|
MsgBoxCloseCallback callback, void * opaque, const char * fmt, ...);
|
||||||
|
|
||||||
typedef void (*MsgBoxConfirmCallback)(bool yes, void * opaque);
|
typedef void (*MsgBoxConfirmCallback)(bool yes, void * opaque);
|
||||||
MsgBoxHandle app_confirmMsgBox(const char * caption,
|
MsgBoxHandle app_confirmMsgBox(const char * caption,
|
||||||
MsgBoxConfirmCallback callback, void * opaque, const char * fmt, ...);
|
MsgBoxConfirmCallback callback, void * opaque, const char * fmt, ...);
|
||||||
|
|||||||
@@ -70,6 +70,79 @@ enum
|
|||||||
|
|
||||||
typedef uint32_t LG_TransportFeatureFlags;
|
typedef uint32_t LG_TransportFeatureFlags;
|
||||||
|
|
||||||
|
typedef struct LG_VersionMismatch
|
||||||
|
{
|
||||||
|
bool valid;
|
||||||
|
char component[16];
|
||||||
|
uint32_t expectedVersion;
|
||||||
|
uint32_t currentVersion;
|
||||||
|
}
|
||||||
|
LG_VersionMismatch;
|
||||||
|
|
||||||
|
#define LG_RECOVERY_MAX_VERSIONS 4
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
LG_RECOVERY_CAP_DISPLAY = 0x1,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef uint32_t LG_RecoveryCaps;
|
||||||
|
|
||||||
|
typedef enum LG_RecoveryRequest
|
||||||
|
{
|
||||||
|
LG_RECOVERY_REQ_NONE,
|
||||||
|
LG_RECOVERY_REQ_NORMAL,
|
||||||
|
LG_RECOVERY_REQ_RECOVERY,
|
||||||
|
}
|
||||||
|
LG_RecoveryRequest;
|
||||||
|
|
||||||
|
typedef enum LG_RecoveryState
|
||||||
|
{
|
||||||
|
LG_RECOVERY_STATE_UNKNOWN,
|
||||||
|
LG_RECOVERY_STATE_NORMAL,
|
||||||
|
LG_RECOVERY_STATE_SWITCHING,
|
||||||
|
LG_RECOVERY_STATE_ACTIVE,
|
||||||
|
LG_RECOVERY_STATE_FAILED,
|
||||||
|
}
|
||||||
|
LG_RecoveryState;
|
||||||
|
|
||||||
|
typedef enum LG_RecoveryError
|
||||||
|
{
|
||||||
|
LG_RECOVERY_ERR_NONE,
|
||||||
|
LG_RECOVERY_ERR_UNSUPPORTED,
|
||||||
|
LG_RECOVERY_ERR_HELPER_UNAVAILABLE,
|
||||||
|
LG_RECOVERY_ERR_TOPOLOGY_FAILED,
|
||||||
|
LG_RECOVERY_ERR_NO_FALLBACK_DISPLAY,
|
||||||
|
}
|
||||||
|
LG_RecoveryError;
|
||||||
|
|
||||||
|
typedef struct LG_RecoveryVersion
|
||||||
|
{
|
||||||
|
char component[16];
|
||||||
|
uint32_t version;
|
||||||
|
}
|
||||||
|
LG_RecoveryVersion;
|
||||||
|
|
||||||
|
typedef struct LG_RecoveryInfo
|
||||||
|
{
|
||||||
|
uint32_t abiVersion;
|
||||||
|
LG_RecoveryCaps capabilities;
|
||||||
|
uint64_t instance;
|
||||||
|
uint32_t heartbeat;
|
||||||
|
bool uuidValid;
|
||||||
|
uint8_t uuid[16];
|
||||||
|
char producerVersion[64];
|
||||||
|
uint32_t versionCount;
|
||||||
|
LG_RecoveryVersion versions[LG_RECOVERY_MAX_VERSIONS];
|
||||||
|
uint32_t requestSerial;
|
||||||
|
LG_RecoveryRequest request;
|
||||||
|
uint32_t ackSerial;
|
||||||
|
LG_RecoveryRequest ackRequest;
|
||||||
|
LG_RecoveryState state;
|
||||||
|
LG_RecoveryError error;
|
||||||
|
}
|
||||||
|
LG_RecoveryInfo;
|
||||||
|
|
||||||
typedef struct LG_TransportSession
|
typedef struct LG_TransportSession
|
||||||
{
|
{
|
||||||
char version[32];
|
char version[32];
|
||||||
@@ -87,7 +160,7 @@ typedef struct LG_TransportSession
|
|||||||
uint8_t cores;
|
uint8_t cores;
|
||||||
uint8_t sockets;
|
uint8_t sockets;
|
||||||
|
|
||||||
uint32_t remoteVersion;
|
LG_VersionMismatch versionMismatch;
|
||||||
}
|
}
|
||||||
LG_TransportSession;
|
LG_TransportSession;
|
||||||
|
|
||||||
@@ -346,6 +419,13 @@ typedef struct LG_TransportOps
|
|||||||
const LG_ClipboardOps *(*getClipboardOps)(LG_Transport * transport,
|
const LG_ClipboardOps *(*getClipboardOps)(LG_Transport * transport,
|
||||||
void ** opaque);
|
void ** opaque);
|
||||||
|
|
||||||
|
/* Recovery operations are independent of a transport session and may be
|
||||||
|
* used after create whenever the backing transport advertises them. */
|
||||||
|
LG_TransportStatus (*getRecoveryInfo)(LG_Transport * transport,
|
||||||
|
LG_RecoveryInfo * info);
|
||||||
|
LG_TransportStatus (*requestRecovery)(LG_Transport * transport,
|
||||||
|
LG_RecoveryRequest request, uint32_t * serial);
|
||||||
|
|
||||||
LG_TransportStatus (*sendControl)(LG_Transport * transport,
|
LG_TransportStatus (*sendControl)(LG_Transport * transport,
|
||||||
const LG_TransportControl * control, LG_TransportControlToken * token);
|
const LG_TransportControl * control, LG_TransportControlToken * token);
|
||||||
LG_TransportStatus (*controlStatus)(LG_Transport * transport,
|
LG_TransportStatus (*controlStatus)(LG_Transport * transport,
|
||||||
|
|||||||
@@ -712,7 +712,20 @@ MsgBoxHandle app_msgBox(const char * caption, const char * fmt, ...)
|
|||||||
{
|
{
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
MsgBoxHandle handle = overlayMsg_show(caption, NULL, NULL, fmt, args);
|
MsgBoxHandle handle = overlayMsg_show(
|
||||||
|
caption, NULL, NULL, NULL, NULL, fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
MsgBoxHandle app_msgBoxWithClose(const char * caption,
|
||||||
|
MsgBoxCloseCallback callback, void * opaque, const char * fmt, ...)
|
||||||
|
{
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
MsgBoxHandle handle = overlayMsg_show(
|
||||||
|
caption, NULL, NULL, callback, opaque, fmt, args);
|
||||||
va_end(args);
|
va_end(args);
|
||||||
|
|
||||||
return handle;
|
return handle;
|
||||||
@@ -723,7 +736,8 @@ MsgBoxHandle app_confirmMsgBox(const char * caption,
|
|||||||
{
|
{
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
MsgBoxHandle handle = overlayMsg_show(caption, callback, opaque, fmt, args);
|
MsgBoxHandle handle = overlayMsg_show(
|
||||||
|
caption, callback, opaque, NULL, NULL, fmt, args);
|
||||||
va_end(args);
|
va_end(args);
|
||||||
|
|
||||||
return handle;
|
return handle;
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ static int renderThread(void * unused);
|
|||||||
static RenderQueueSource renderQueueSource(LG_VideoSource source);
|
static RenderQueueSource renderQueueSource(LG_VideoSource source);
|
||||||
static bool videoSourceInvalidate(LG_VideoSource source);
|
static bool videoSourceInvalidate(LG_VideoSource source);
|
||||||
static void videoSourceShowSplashIfNeeded(void);
|
static void videoSourceShowSplashIfNeeded(void);
|
||||||
|
static bool fallbackRequestVideo(void);
|
||||||
|
|
||||||
static LGEvent *e_startup = NULL;
|
static LGEvent *e_startup = NULL;
|
||||||
static LGEvent *e_cursorRepaint = NULL;
|
static LGEvent *e_cursorRepaint = NULL;
|
||||||
@@ -1608,7 +1609,7 @@ int main_frameThread(void * unused)
|
|||||||
&g_state.videoSource[LG_VIDEO_SOURCE_PRIMARY].ready, false,
|
&g_state.videoSource[LG_VIDEO_SOURCE_PRIMARY].ready, false,
|
||||||
memory_order_release);
|
memory_order_release);
|
||||||
|
|
||||||
if (!app_useVideoSource(LG_VIDEO_SOURCE_FALLBACK))
|
if (!fallbackRequestVideo())
|
||||||
videoSourceShowSplashIfNeeded();
|
videoSourceShowSplashIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1645,13 +1646,12 @@ static RenderQueueSource renderQueueSource(LG_VideoSource source)
|
|||||||
return RENDER_QUEUE_SOURCE_NONE;
|
return RENDER_QUEUE_SOURCE_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void videoSourceBegin(LG_VideoSource source)
|
static void videoSourceBeginLocked(LG_VideoSource source)
|
||||||
{
|
{
|
||||||
struct VideoSourceState * state = &g_state.videoSource[source];
|
struct VideoSourceState * state = &g_state.videoSource[source];
|
||||||
const uint64_t generation =
|
const uint64_t generation =
|
||||||
renderQueue_sourceBegin(renderQueueSource(source));
|
renderQueue_sourceBegin(renderQueueSource(source));
|
||||||
|
|
||||||
LG_LOCK(g_state.videoSourceLock);
|
|
||||||
atomic_store_explicit(&state->ready, false, memory_order_release);
|
atomic_store_explicit(&state->ready, false, memory_order_release);
|
||||||
atomic_store_explicit(
|
atomic_store_explicit(
|
||||||
&state->transitionSerial, 0, memory_order_relaxed);
|
&state->transitionSerial, 0, memory_order_relaxed);
|
||||||
@@ -1664,6 +1664,12 @@ static void videoSourceBegin(LG_VideoSource source)
|
|||||||
atomic_store_explicit(
|
atomic_store_explicit(
|
||||||
&state->generation, generation, memory_order_release);
|
&state->generation, generation, memory_order_release);
|
||||||
state->configurePending = false;
|
state->configurePending = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void videoSourceBegin(LG_VideoSource source)
|
||||||
|
{
|
||||||
|
LG_LOCK(g_state.videoSourceLock);
|
||||||
|
videoSourceBeginLocked(source);
|
||||||
LG_UNLOCK(g_state.videoSourceLock);
|
LG_UNLOCK(g_state.videoSourceLock);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1883,15 +1889,27 @@ static void videoSourceApplied(void * opaque, RenderQueueSource queueSource,
|
|||||||
app_refreshVideoSource();
|
app_refreshVideoSource();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool swSurfaceEventAdmitted(LG_VideoSource source)
|
||||||
|
{
|
||||||
|
return source != LG_VIDEO_SOURCE_FALLBACK ||
|
||||||
|
lgTransportFallback_admitted(g_state.fallback);
|
||||||
|
}
|
||||||
|
|
||||||
static void swSurfaceConfigure(LG_VideoSource source,
|
static void swSurfaceConfigure(LG_VideoSource source,
|
||||||
unsigned int width, unsigned int height)
|
unsigned int width, unsigned int height)
|
||||||
{
|
{
|
||||||
struct VideoSourceState * state = &g_state.videoSource[source];
|
struct VideoSourceState * state = &g_state.videoSource[source];
|
||||||
videoSourceBegin(source);
|
LG_LOCK(g_state.videoSourceLock);
|
||||||
|
if (!swSurfaceEventAdmitted(source))
|
||||||
|
{
|
||||||
|
LG_UNLOCK(g_state.videoSourceLock);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
videoSourceBeginLocked(source);
|
||||||
atomic_store_explicit(&state->width, width, memory_order_relaxed);
|
atomic_store_explicit(&state->width, width, memory_order_relaxed);
|
||||||
atomic_store_explicit(&state->height, height, memory_order_relaxed);
|
atomic_store_explicit(&state->height, height, memory_order_relaxed);
|
||||||
atomic_store_explicit(&state->rotate, LG_ROTATE_0, memory_order_release);
|
atomic_store_explicit(&state->rotate, LG_ROTATE_0, memory_order_release);
|
||||||
LG_LOCK(g_state.videoSourceLock);
|
|
||||||
state->configurePending = true;
|
state->configurePending = true;
|
||||||
atomic_store_explicit(&state->ready, true, memory_order_release);
|
atomic_store_explicit(&state->ready, true, memory_order_release);
|
||||||
LG_UNLOCK(g_state.videoSourceLock);
|
LG_UNLOCK(g_state.videoSourceLock);
|
||||||
@@ -1924,30 +1942,41 @@ static void swSurfaceDestroy(LG_VideoSource source)
|
|||||||
static void swSurfaceDrawFill(LG_VideoSource source, int x, int y,
|
static void swSurfaceDrawFill(LG_VideoSource source, int x, int y,
|
||||||
int width, int height, uint32_t color)
|
int width, int height, uint32_t color)
|
||||||
{
|
{
|
||||||
|
LG_LOCK(g_state.videoSourceLock);
|
||||||
|
if (!swSurfaceEventAdmitted(source))
|
||||||
|
{
|
||||||
|
LG_UNLOCK(g_state.videoSourceLock);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const uint64_t generation = atomic_load_explicit(
|
const uint64_t generation = atomic_load_explicit(
|
||||||
&g_state.videoSource[source].generation, memory_order_acquire);
|
&g_state.videoSource[source].generation, memory_order_acquire);
|
||||||
renderQueue_sourceSwSurfaceDrawFill(renderQueueSource(source), generation,
|
renderQueue_sourceSwSurfaceDrawFill(renderQueueSource(source), generation,
|
||||||
x, y, width, height, color);
|
x, y, width, height, color);
|
||||||
|
LG_UNLOCK(g_state.videoSourceLock);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void swSurfaceDrawBitmap(LG_VideoSource source, bool topDown,
|
static void swSurfaceDrawBitmap(LG_VideoSource source, bool topDown,
|
||||||
int x, int y, int width, int height, int stride, const void * data)
|
int x, int y, int width, int height, int stride, const void * data)
|
||||||
{
|
{
|
||||||
|
LG_LOCK(g_state.videoSourceLock);
|
||||||
|
if (!swSurfaceEventAdmitted(source))
|
||||||
|
{
|
||||||
|
LG_UNLOCK(g_state.videoSourceLock);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const uint64_t generation = atomic_load_explicit(
|
const uint64_t generation = atomic_load_explicit(
|
||||||
&g_state.videoSource[source].generation, memory_order_acquire);
|
&g_state.videoSource[source].generation, memory_order_acquire);
|
||||||
renderQueue_sourceSwSurfaceDrawBitmap(renderQueueSource(source), generation,
|
renderQueue_sourceSwSurfaceDrawBitmap(renderQueueSource(source), generation,
|
||||||
x, y, width, height, stride, data, topDown);
|
x, y, width, height, stride, data, topDown);
|
||||||
|
LG_UNLOCK(g_state.videoSourceLock);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void swSurfacePointer(LG_VideoSource source,
|
static void swSurfacePointer(LG_VideoSource source,
|
||||||
const LG_TransportPointer * pointer)
|
const LG_TransportPointer * pointer)
|
||||||
{
|
{
|
||||||
struct VideoSourceState * state = &g_state.videoSource[source];
|
struct VideoSourceState * state = &g_state.videoSource[source];
|
||||||
const uint64_t generation = atomic_load_explicit(
|
|
||||||
&state->generation, memory_order_acquire);
|
|
||||||
if (!generation)
|
|
||||||
return;
|
|
||||||
|
|
||||||
LG_RendererCursor type = LG_CURSOR_COLOR;
|
LG_RendererCursor type = LG_CURSOR_COLOR;
|
||||||
if (pointer->flags & LG_TRANSPORT_POINTER_SHAPE)
|
if (pointer->flags & LG_TRANSPORT_POINTER_SHAPE)
|
||||||
switch (pointer->type)
|
switch (pointer->type)
|
||||||
@@ -1971,8 +2000,15 @@ static void swSurfacePointer(LG_VideoSource source,
|
|||||||
const bool drawCursor = source != LG_VIDEO_SOURCE_PRIMARY ||
|
const bool drawCursor = source != LG_VIDEO_SOURCE_PRIMARY ||
|
||||||
g_cursor.draw || !lgInput_available();
|
g_cursor.draw || !lgInput_available();
|
||||||
LG_LOCK(g_state.videoSourceLock);
|
LG_LOCK(g_state.videoSourceLock);
|
||||||
if (atomic_load_explicit(&state->generation, memory_order_acquire) !=
|
if (!swSurfaceEventAdmitted(source))
|
||||||
generation)
|
{
|
||||||
|
LG_UNLOCK(g_state.videoSourceLock);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint64_t generation = atomic_load_explicit(
|
||||||
|
&state->generation, memory_order_acquire);
|
||||||
|
if (!generation)
|
||||||
{
|
{
|
||||||
LG_UNLOCK(g_state.videoSourceLock);
|
LG_UNLOCK(g_state.videoSourceLock);
|
||||||
return;
|
return;
|
||||||
@@ -2036,7 +2072,6 @@ static void swSurfacePointer(LG_VideoSource source,
|
|||||||
g_cursor.guest.hy = hy;
|
g_cursor.guest.hy = hy;
|
||||||
g_cursor.guest.valid = valid;
|
g_cursor.guest.valid = valid;
|
||||||
}
|
}
|
||||||
LG_UNLOCK(g_state.videoSourceLock);
|
|
||||||
|
|
||||||
if (pointer->flags & LG_TRANSPORT_POINTER_SHAPE)
|
if (pointer->flags & LG_TRANSPORT_POINTER_SHAPE)
|
||||||
renderQueue_sourceCursorImage(renderQueueSource(source), generation,
|
renderQueue_sourceCursorImage(renderQueueSource(source), generation,
|
||||||
@@ -2055,6 +2090,7 @@ static void swSurfacePointer(LG_VideoSource source,
|
|||||||
LG_TRANSPORT_POINTER_VISIBLE_VALID | LG_TRANSPORT_POINTER_SHAPE)))
|
LG_TRANSPORT_POINTER_VISIBLE_VALID | LG_TRANSPORT_POINTER_SHAPE)))
|
||||||
renderQueue_sourceCursorState(renderQueueSource(source), generation,
|
renderQueue_sourceCursorState(renderQueueSource(source), generation,
|
||||||
visible, x, y, hx, hy);
|
visible, x, y, hx, hy);
|
||||||
|
LG_UNLOCK(g_state.videoSourceLock);
|
||||||
|
|
||||||
if (sourceApplied)
|
if (sourceApplied)
|
||||||
{
|
{
|
||||||
@@ -2094,8 +2130,7 @@ static void swSurfaceEventDestroy(void * opaque)
|
|||||||
static void swSurfaceEventDrawFill(void * opaque, int x, int y,
|
static void swSurfaceEventDrawFill(void * opaque, int x, int y,
|
||||||
int width, int height, uint32_t color)
|
int width, int height, uint32_t color)
|
||||||
{
|
{
|
||||||
swSurfaceDrawFill(
|
swSurfaceDrawFill(swSurfaceSource(opaque), x, y, width, height, color);
|
||||||
swSurfaceSource(opaque), x, y, width, height, color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void swSurfaceEventDrawBitmap(void * opaque, bool topDown,
|
static void swSurfaceEventDrawBitmap(void * opaque, bool topDown,
|
||||||
@@ -2120,6 +2155,18 @@ static const LG_SwSurfaceEventOps swSurfaceEvents =
|
|||||||
.pointer = swSurfaceEventPointer,
|
.pointer = swSurfaceEventPointer,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static bool fallbackRequestVideo(void)
|
||||||
|
{
|
||||||
|
if (!g_state.fallback)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
lgTransportFallback_requestVideoActive(g_state.fallback, true);
|
||||||
|
if (!lgTransportFallback_admitted(g_state.fallback))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return app_useVideoSource(LG_VIDEO_SOURCE_FALLBACK);
|
||||||
|
}
|
||||||
|
|
||||||
static void fallbackConnected(void * opaque,
|
static void fallbackConnected(void * opaque,
|
||||||
const LG_TransportSession * session)
|
const LG_TransportSession * session)
|
||||||
{
|
{
|
||||||
@@ -2131,6 +2178,9 @@ static void fallbackConnected(void * opaque,
|
|||||||
&g_state.lgHostConnected, memory_order_acquire))
|
&g_state.lgHostConnected, memory_order_acquire))
|
||||||
core_setTitle(session->name);
|
core_setTitle(session->name);
|
||||||
|
|
||||||
|
if (lgTransportFallback_videoRequested(g_state.fallback))
|
||||||
|
fallbackRequestVideo();
|
||||||
|
else
|
||||||
app_refreshVideoSource();
|
app_refreshVideoSource();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2162,7 +2212,7 @@ static const LG_TransportFallbackEventOps fallbackEvents =
|
|||||||
.uuidMismatch = fallbackUUIDMismatch,
|
.uuidMismatch = fallbackUUIDMismatch,
|
||||||
};
|
};
|
||||||
|
|
||||||
static bool fallbackStart(void)
|
static bool fallbackStart(const uint8_t primaryUUID[16])
|
||||||
{
|
{
|
||||||
if (!option_get_bool("spice", "enable") ||
|
if (!option_get_bool("spice", "enable") ||
|
||||||
strcmp(g_params.transport, "spice") == 0)
|
strcmp(g_params.transport, "spice") == 0)
|
||||||
@@ -2170,7 +2220,7 @@ static bool fallbackStart(void)
|
|||||||
|
|
||||||
if (lgTransportFallback_start("spice", &swSurfaceEvents,
|
if (lgTransportFallback_start("spice", &swSurfaceEvents,
|
||||||
(void *)(uintptr_t)LG_VIDEO_SOURCE_FALLBACK,
|
(void *)(uintptr_t)LG_VIDEO_SOURCE_FALLBACK,
|
||||||
&fallbackEvents, NULL, &g_state.fallback))
|
&fallbackEvents, NULL, primaryUUID, &g_state.fallback))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
DEBUG_ERROR("Failed to start the SPICE fallback transport");
|
DEBUG_ERROR("Failed to start the SPICE fallback transport");
|
||||||
@@ -2251,12 +2301,522 @@ static bool tryRenderer(const int index, const LG_RendererParams lgrParams,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void reportBadVersion(void)
|
static void reportBadVersion(const LG_VersionMismatch * mismatch)
|
||||||
{
|
{
|
||||||
DEBUG_BREAK();
|
DEBUG_BREAK();
|
||||||
DEBUG_ERROR("The host application is not compatible with this client");
|
if (mismatch->valid)
|
||||||
DEBUG_ERROR("This is not a Looking Glass error, do not report this");
|
{
|
||||||
DEBUG_ERROR("Please install the matching host application for this client");
|
DEBUG_ERROR("Incompatible %s version", mismatch->component);
|
||||||
|
DEBUG_ERROR("Expected version: %u", mismatch->expectedVersion);
|
||||||
|
DEBUG_ERROR("Current version : %u", mismatch->currentVersion);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
DEBUG_ERROR("The transport is not compatible with this client");
|
||||||
|
DEBUG_ERROR("Please install matching Looking Glass components");
|
||||||
|
}
|
||||||
|
|
||||||
|
static const int RECOVERY_PENDING = -1;
|
||||||
|
static const int RECOVERY_NO = 0;
|
||||||
|
static const int RECOVERY_YES = 1;
|
||||||
|
|
||||||
|
struct RecoveryPrompt
|
||||||
|
{
|
||||||
|
atomic_int choice;
|
||||||
|
atomic_uintptr_t handle;
|
||||||
|
atomic_uintptr_t message;
|
||||||
|
atomic_bool messageClosed;
|
||||||
|
uint64_t instance;
|
||||||
|
uint32_t serial;
|
||||||
|
uint32_t failedSerial;
|
||||||
|
LG_RecoveryState reportedState;
|
||||||
|
bool shown;
|
||||||
|
bool requested;
|
||||||
|
bool owned;
|
||||||
|
bool retryPrompt;
|
||||||
|
bool retryDeclined;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool recoveryGetInfo(LG_RecoveryInfo * info)
|
||||||
|
{
|
||||||
|
if (!g_state.transport.ops->getRecoveryInfo)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return g_state.transport.ops->getRecoveryInfo(
|
||||||
|
g_state.transport.handle, info) == LG_TRANSPORT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void recoveryConfirm(bool yes, void * opaque)
|
||||||
|
{
|
||||||
|
struct RecoveryPrompt * prompt = opaque;
|
||||||
|
atomic_store_explicit(&prompt->choice,
|
||||||
|
yes ? RECOVERY_YES : RECOVERY_NO, memory_order_release);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void recoveryClosePrompt(struct RecoveryPrompt * prompt)
|
||||||
|
{
|
||||||
|
if (atomic_load_explicit(
|
||||||
|
&prompt->choice, memory_order_acquire) != RECOVERY_PENDING)
|
||||||
|
{
|
||||||
|
atomic_store_explicit(&prompt->handle, 0, memory_order_relaxed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MsgBoxHandle handle = (MsgBoxHandle)atomic_exchange_explicit(
|
||||||
|
&prompt->handle, 0, memory_order_acq_rel);
|
||||||
|
app_msgBoxClose(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void recoveryCloseMessage(struct RecoveryPrompt * prompt)
|
||||||
|
{
|
||||||
|
MsgBoxHandle handle = (MsgBoxHandle)atomic_exchange_explicit(
|
||||||
|
&prompt->message, 0, memory_order_acq_rel);
|
||||||
|
app_msgBoxClose(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void recoveryMessageClosed(MsgBoxHandle handle, void * opaque)
|
||||||
|
{
|
||||||
|
struct RecoveryPrompt * prompt = opaque;
|
||||||
|
atomic_store_explicit(
|
||||||
|
&prompt->messageClosed, true, memory_order_release);
|
||||||
|
|
||||||
|
uintptr_t expected = (uintptr_t)handle;
|
||||||
|
atomic_compare_exchange_strong_explicit(&prompt->message,
|
||||||
|
&expected, 0, memory_order_acq_rel, memory_order_acquire);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void recoveryBeginMessage(struct RecoveryPrompt * prompt)
|
||||||
|
{
|
||||||
|
recoveryCloseMessage(prompt);
|
||||||
|
atomic_store_explicit(
|
||||||
|
&prompt->messageClosed, false, memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void recoveryStoreMessage(struct RecoveryPrompt * prompt,
|
||||||
|
MsgBoxHandle handle)
|
||||||
|
{
|
||||||
|
atomic_store_explicit(
|
||||||
|
&prompt->message, (uintptr_t)handle, memory_order_release);
|
||||||
|
if (!atomic_load_explicit(
|
||||||
|
&prompt->messageClosed, memory_order_acquire))
|
||||||
|
return;
|
||||||
|
|
||||||
|
uintptr_t expected = (uintptr_t)handle;
|
||||||
|
atomic_compare_exchange_strong_explicit(&prompt->message,
|
||||||
|
&expected, 0, memory_order_acq_rel, memory_order_acquire);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void recoveryClose(struct RecoveryPrompt * prompt)
|
||||||
|
{
|
||||||
|
recoveryClosePrompt(prompt);
|
||||||
|
recoveryCloseMessage(prompt);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int recoveryExit(struct RecoveryPrompt * prompt, int result)
|
||||||
|
{
|
||||||
|
recoveryClose(prompt);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void retainMessage(MsgBoxHandle * messages, size_t capacity,
|
||||||
|
int * count, MsgBoxHandle message)
|
||||||
|
{
|
||||||
|
if (!message)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ((size_t)*count >= capacity)
|
||||||
|
{
|
||||||
|
app_msgBoxClose(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
messages[(*count)++] = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void recoveryShowPrompt(struct RecoveryPrompt * prompt,
|
||||||
|
const LG_VersionMismatch * mismatch, bool hasFallback)
|
||||||
|
{
|
||||||
|
const char * fallback = hasFallback ?
|
||||||
|
"The client will then switch to its SPICE fallback." :
|
||||||
|
"SPICE is disabled in this client, so use another SPICE viewer.";
|
||||||
|
|
||||||
|
recoveryCloseMessage(prompt);
|
||||||
|
prompt->shown = true;
|
||||||
|
prompt->retryPrompt = false;
|
||||||
|
atomic_store_explicit(
|
||||||
|
&prompt->choice, RECOVERY_PENDING, memory_order_relaxed);
|
||||||
|
MsgBoxHandle handle;
|
||||||
|
if (mismatch->valid)
|
||||||
|
{
|
||||||
|
const char * component = mismatch->component[0] ?
|
||||||
|
mismatch->component : "transport";
|
||||||
|
handle = app_confirmMsgBox(
|
||||||
|
"Incompatible Transport Version", recoveryConfirm, prompt,
|
||||||
|
"Expected %s version: %u\n"
|
||||||
|
"Current %s version: %u\n"
|
||||||
|
"\n"
|
||||||
|
"Enable recovery mode to restore the guest display topology?\n"
|
||||||
|
"%s",
|
||||||
|
component, mismatch->expectedVersion,
|
||||||
|
component, mismatch->currentVersion, fallback);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
handle = app_confirmMsgBox(
|
||||||
|
"Incompatible Transport Version", recoveryConfirm, prompt,
|
||||||
|
"The transport is not compatible with this client.\n"
|
||||||
|
"\n"
|
||||||
|
"Enable recovery mode to restore the guest display topology?\n"
|
||||||
|
"%s", fallback);
|
||||||
|
}
|
||||||
|
atomic_store_explicit(
|
||||||
|
&prompt->handle, (uintptr_t)handle, memory_order_release);
|
||||||
|
if (atomic_load_explicit(
|
||||||
|
&prompt->choice, memory_order_acquire) != RECOVERY_PENDING)
|
||||||
|
atomic_store_explicit(&prompt->handle, 0, memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char * recoveryErrorText(LG_RecoveryError error)
|
||||||
|
{
|
||||||
|
switch (error)
|
||||||
|
{
|
||||||
|
case LG_RECOVERY_ERR_HELPER_UNAVAILABLE:
|
||||||
|
return "The IDD helper is unavailable";
|
||||||
|
|
||||||
|
case LG_RECOVERY_ERR_TOPOLOGY_FAILED:
|
||||||
|
return "Windows could not restore the saved display topology";
|
||||||
|
|
||||||
|
case LG_RECOVERY_ERR_NO_FALLBACK_DISPLAY:
|
||||||
|
return "No fallback display became active";
|
||||||
|
|
||||||
|
case LG_RECOVERY_ERR_UNSUPPORTED:
|
||||||
|
return "Recovery is not supported by the guest driver";
|
||||||
|
|
||||||
|
case LG_RECOVERY_ERR_NONE:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "The guest could not enter recovery mode";
|
||||||
|
}
|
||||||
|
|
||||||
|
static int recoveryTakeChoice(struct RecoveryPrompt * prompt)
|
||||||
|
{
|
||||||
|
const int choice = atomic_exchange_explicit(
|
||||||
|
&prompt->choice, RECOVERY_PENDING, memory_order_acq_rel);
|
||||||
|
if (choice != RECOVERY_PENDING)
|
||||||
|
atomic_store_explicit(&prompt->handle, 0, memory_order_relaxed);
|
||||||
|
return choice;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void recoveryShowRetry(struct RecoveryPrompt * prompt,
|
||||||
|
const char * reason, uint32_t failedSerial)
|
||||||
|
{
|
||||||
|
recoveryClose(prompt);
|
||||||
|
if (failedSerial)
|
||||||
|
prompt->failedSerial = failedSerial;
|
||||||
|
prompt->serial = 0;
|
||||||
|
prompt->reportedState = LG_RECOVERY_STATE_FAILED;
|
||||||
|
prompt->shown = true;
|
||||||
|
prompt->requested = false;
|
||||||
|
prompt->owned = false;
|
||||||
|
prompt->retryPrompt = true;
|
||||||
|
atomic_store_explicit(
|
||||||
|
&prompt->choice, RECOVERY_PENDING, memory_order_relaxed);
|
||||||
|
MsgBoxHandle handle = app_confirmMsgBox(
|
||||||
|
"Recovery Failed", recoveryConfirm, prompt,
|
||||||
|
"%s.\n\nRetry guest display recovery?", reason);
|
||||||
|
atomic_store_explicit(
|
||||||
|
&prompt->handle, (uintptr_t)handle, memory_order_release);
|
||||||
|
if (atomic_load_explicit(
|
||||||
|
&prompt->choice, memory_order_acquire) != RECOVERY_PENDING)
|
||||||
|
atomic_store_explicit(&prompt->handle, 0, memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void recoveryShowPending(struct RecoveryPrompt * prompt,
|
||||||
|
const LG_VersionMismatch * mismatch)
|
||||||
|
{
|
||||||
|
prompt->shown = true;
|
||||||
|
prompt->reportedState = LG_RECOVERY_STATE_SWITCHING;
|
||||||
|
prompt->retryPrompt = false;
|
||||||
|
if (mismatch->valid)
|
||||||
|
{
|
||||||
|
const char * component = mismatch->component[0] ?
|
||||||
|
mismatch->component : "transport";
|
||||||
|
recoveryBeginMessage(prompt);
|
||||||
|
recoveryStoreMessage(prompt, app_msgBoxWithClose(
|
||||||
|
"Incompatible Transport Version",
|
||||||
|
recoveryMessageClosed, prompt,
|
||||||
|
"Expected %s version: %u\n"
|
||||||
|
"Current %s version: %u\n"
|
||||||
|
"\n"
|
||||||
|
"Recovery mode is pending. Waiting for the guest driver.",
|
||||||
|
component, mismatch->expectedVersion,
|
||||||
|
component, mismatch->currentVersion));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
recoveryBeginMessage(prompt);
|
||||||
|
recoveryStoreMessage(prompt, app_msgBoxWithClose(
|
||||||
|
"Incompatible Transport Version",
|
||||||
|
recoveryMessageClosed, prompt,
|
||||||
|
"The transport is not compatible with this client.\n"
|
||||||
|
"\n"
|
||||||
|
"Recovery mode is pending. Waiting for the guest driver."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool recoverySerialNewer(uint32_t serial, uint32_t reference)
|
||||||
|
{
|
||||||
|
const uint32_t difference = serial - reference;
|
||||||
|
return difference && difference < 0x80000000U;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void recoveryHandleMismatch(struct RecoveryPrompt * prompt,
|
||||||
|
const LG_VersionMismatch * mismatch)
|
||||||
|
{
|
||||||
|
LG_RecoveryInfo info = { 0 };
|
||||||
|
const bool available = recoveryGetInfo(&info) &&
|
||||||
|
(info.capabilities & LG_RECOVERY_CAP_DISPLAY);
|
||||||
|
if (!available)
|
||||||
|
{
|
||||||
|
if (prompt->requested && !prompt->retryPrompt &&
|
||||||
|
!prompt->retryDeclined)
|
||||||
|
{
|
||||||
|
recoveryShowRetry(prompt,
|
||||||
|
"The guest recovery channel is unavailable", 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prompt->shown)
|
||||||
|
return;
|
||||||
|
|
||||||
|
prompt->shown = true;
|
||||||
|
if (mismatch->valid)
|
||||||
|
{
|
||||||
|
const char * component = mismatch->component[0] ?
|
||||||
|
mismatch->component : "transport";
|
||||||
|
recoveryBeginMessage(prompt);
|
||||||
|
recoveryStoreMessage(prompt, app_msgBoxWithClose(
|
||||||
|
"Incompatible Transport Version",
|
||||||
|
recoveryMessageClosed, prompt,
|
||||||
|
"Expected %s version: %u\n"
|
||||||
|
"Current %s version: %u\n"
|
||||||
|
"\n"
|
||||||
|
"This guest driver does not support remote recovery.\n"
|
||||||
|
"Use a guest console to install matching components.",
|
||||||
|
component, mismatch->expectedVersion,
|
||||||
|
component, mismatch->currentVersion));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
recoveryBeginMessage(prompt);
|
||||||
|
recoveryStoreMessage(prompt, app_msgBoxWithClose(
|
||||||
|
"Incompatible Transport Version",
|
||||||
|
recoveryMessageClosed, prompt,
|
||||||
|
"The transport is not compatible with this client.\n"
|
||||||
|
"\n"
|
||||||
|
"This guest driver does not support remote recovery.\n"
|
||||||
|
"Use a guest console to install matching components."));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prompt->instance != info.instance)
|
||||||
|
{
|
||||||
|
recoveryClose(prompt);
|
||||||
|
prompt->instance = info.instance;
|
||||||
|
prompt->serial = 0;
|
||||||
|
prompt->failedSerial = 0;
|
||||||
|
prompt->reportedState = LG_RECOVERY_STATE_UNKNOWN;
|
||||||
|
prompt->shown = false;
|
||||||
|
prompt->requested = false;
|
||||||
|
prompt->owned = false;
|
||||||
|
prompt->retryPrompt = false;
|
||||||
|
prompt->retryDeclined = false;
|
||||||
|
atomic_store_explicit(
|
||||||
|
&prompt->choice, RECOVERY_PENDING, memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.uuidValid)
|
||||||
|
lgTransportFallback_setPrimaryUUID(g_state.fallback, info.uuid);
|
||||||
|
else
|
||||||
|
lgTransportFallback_clearPrimaryUUID(g_state.fallback);
|
||||||
|
|
||||||
|
const bool requestNewer = info.requestSerial != 0 &&
|
||||||
|
(info.ackSerial == 0 ||
|
||||||
|
recoverySerialNewer(info.requestSerial, info.ackSerial));
|
||||||
|
const LG_RecoveryRequest globalRequest = requestNewer ?
|
||||||
|
info.request : info.ackRequest;
|
||||||
|
const uint32_t globalSerial = requestNewer ?
|
||||||
|
info.requestSerial : info.ackSerial;
|
||||||
|
if (prompt->requested && globalSerial &&
|
||||||
|
globalRequest == LG_RECOVERY_REQ_NORMAL &&
|
||||||
|
(globalSerial == prompt->serial ||
|
||||||
|
recoverySerialNewer(globalSerial, prompt->serial)))
|
||||||
|
{
|
||||||
|
recoveryClose(prompt);
|
||||||
|
prompt->serial = 0;
|
||||||
|
prompt->reportedState = LG_RECOVERY_STATE_UNKNOWN;
|
||||||
|
prompt->shown = prompt->retryDeclined;
|
||||||
|
prompt->requested = false;
|
||||||
|
prompt->owned = false;
|
||||||
|
prompt->retryPrompt = false;
|
||||||
|
atomic_store_explicit(
|
||||||
|
&prompt->choice, RECOVERY_PENDING, memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool pendingRecovery = requestNewer &&
|
||||||
|
info.request == LG_RECOVERY_REQ_RECOVERY;
|
||||||
|
const bool failedRecovery = !requestNewer && info.ackSerial != 0 &&
|
||||||
|
info.ackRequest == LG_RECOVERY_REQ_RECOVERY &&
|
||||||
|
info.state == LG_RECOVERY_STATE_FAILED;
|
||||||
|
const bool statusRecovery = !requestNewer && info.ackSerial != 0 &&
|
||||||
|
info.ackRequest == LG_RECOVERY_REQ_RECOVERY &&
|
||||||
|
(info.state == LG_RECOVERY_STATE_SWITCHING ||
|
||||||
|
info.state == LG_RECOVERY_STATE_ACTIVE);
|
||||||
|
if (failedRecovery && prompt->requested &&
|
||||||
|
prompt->failedSerial != info.ackSerial &&
|
||||||
|
(info.ackSerial == prompt->serial ||
|
||||||
|
recoverySerialNewer(info.ackSerial, prompt->serial)))
|
||||||
|
{
|
||||||
|
if (prompt->owned && info.ackSerial == prompt->serial)
|
||||||
|
{
|
||||||
|
recoveryShowRetry(prompt, recoveryErrorText(info.error),
|
||||||
|
info.ackSerial);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
recoveryClose(prompt);
|
||||||
|
prompt->serial = 0;
|
||||||
|
prompt->failedSerial = info.ackSerial;
|
||||||
|
prompt->reportedState = LG_RECOVERY_STATE_UNKNOWN;
|
||||||
|
prompt->shown = prompt->retryDeclined;
|
||||||
|
prompt->requested = false;
|
||||||
|
prompt->owned = false;
|
||||||
|
prompt->retryPrompt = false;
|
||||||
|
atomic_store_explicit(
|
||||||
|
&prompt->choice, RECOVERY_PENDING, memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pendingRecovery)
|
||||||
|
{
|
||||||
|
if (!prompt->requested || prompt->serial != info.requestSerial)
|
||||||
|
{
|
||||||
|
recoveryClose(prompt);
|
||||||
|
prompt->requested = true;
|
||||||
|
prompt->serial = info.requestSerial;
|
||||||
|
prompt->failedSerial = 0;
|
||||||
|
prompt->reportedState = LG_RECOVERY_STATE_UNKNOWN;
|
||||||
|
prompt->shown = false;
|
||||||
|
prompt->owned = false;
|
||||||
|
prompt->retryPrompt = false;
|
||||||
|
atomic_store_explicit(
|
||||||
|
&prompt->choice, RECOVERY_PENDING, memory_order_relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (statusRecovery &&
|
||||||
|
(!prompt->requested || prompt->serial != info.ackSerial))
|
||||||
|
{
|
||||||
|
recoveryClose(prompt);
|
||||||
|
prompt->requested = true;
|
||||||
|
prompt->serial = info.ackSerial;
|
||||||
|
prompt->failedSerial = 0;
|
||||||
|
prompt->reportedState = LG_RECOVERY_STATE_UNKNOWN;
|
||||||
|
prompt->shown = false;
|
||||||
|
prompt->owned = false;
|
||||||
|
prompt->retryPrompt = false;
|
||||||
|
atomic_store_explicit(
|
||||||
|
&prompt->choice, RECOVERY_PENDING, memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool retryChoice = prompt->retryPrompt;
|
||||||
|
const int choice = recoveryTakeChoice(prompt);
|
||||||
|
if (choice == RECOVERY_NO)
|
||||||
|
{
|
||||||
|
prompt->retryPrompt = false;
|
||||||
|
if (retryChoice)
|
||||||
|
prompt->retryDeclined = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (choice == RECOVERY_YES)
|
||||||
|
{
|
||||||
|
prompt->retryPrompt = false;
|
||||||
|
prompt->retryDeclined = false;
|
||||||
|
const LG_TransportStatus status =
|
||||||
|
g_state.transport.ops->requestRecovery ?
|
||||||
|
g_state.transport.ops->requestRecovery(g_state.transport.handle,
|
||||||
|
LG_RECOVERY_REQ_RECOVERY, &prompt->serial) :
|
||||||
|
LG_TRANSPORT_UNAVAILABLE;
|
||||||
|
prompt->requested = status == LG_TRANSPORT_OK;
|
||||||
|
prompt->owned = prompt->requested;
|
||||||
|
prompt->reportedState = LG_RECOVERY_STATE_UNKNOWN;
|
||||||
|
if (!prompt->requested)
|
||||||
|
{
|
||||||
|
recoveryShowRetry(prompt,
|
||||||
|
"The recovery request could not be sent to the guest driver", 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt->failedSerial = 0;
|
||||||
|
fallbackRequestVideo();
|
||||||
|
recoveryShowPending(prompt, mismatch);
|
||||||
|
app_alert(LG_ALERT_INFO, "Guest display recovery requested");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool matchingAck = prompt->requested && statusRecovery &&
|
||||||
|
info.ackSerial == prompt->serial;
|
||||||
|
if (matchingAck && info.state == LG_RECOVERY_STATE_ACTIVE)
|
||||||
|
{
|
||||||
|
fallbackRequestVideo();
|
||||||
|
if (prompt->reportedState != LG_RECOVERY_STATE_ACTIVE)
|
||||||
|
{
|
||||||
|
prompt->shown = true;
|
||||||
|
prompt->reportedState = LG_RECOVERY_STATE_ACTIVE;
|
||||||
|
if (mismatch->valid)
|
||||||
|
{
|
||||||
|
const char * component = mismatch->component[0] ?
|
||||||
|
mismatch->component : "transport";
|
||||||
|
recoveryBeginMessage(prompt);
|
||||||
|
recoveryStoreMessage(prompt, app_msgBoxWithClose(
|
||||||
|
"Incompatible Transport Version",
|
||||||
|
recoveryMessageClosed, prompt,
|
||||||
|
"Expected %s version: %u\n"
|
||||||
|
"Current %s version: %u\n"
|
||||||
|
"\n"
|
||||||
|
"Recovery mode is active. SPICE video is available.",
|
||||||
|
component, mismatch->expectedVersion,
|
||||||
|
component, mismatch->currentVersion));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
recoveryBeginMessage(prompt);
|
||||||
|
recoveryStoreMessage(prompt, app_msgBoxWithClose(
|
||||||
|
"Incompatible Transport Version",
|
||||||
|
recoveryMessageClosed, prompt,
|
||||||
|
"The transport is not compatible with this client.\n"
|
||||||
|
"\n"
|
||||||
|
"Recovery mode is active. SPICE video is available."));
|
||||||
|
}
|
||||||
|
app_alert(LG_ALERT_SUCCESS, "Guest display recovery is active");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool pending = prompt->requested &&
|
||||||
|
((pendingRecovery &&
|
||||||
|
info.requestSerial == prompt->serial) ||
|
||||||
|
(matchingAck && info.state == LG_RECOVERY_STATE_SWITCHING));
|
||||||
|
if (pending)
|
||||||
|
{
|
||||||
|
fallbackRequestVideo();
|
||||||
|
if (prompt->reportedState != LG_RECOVERY_STATE_SWITCHING)
|
||||||
|
recoveryShowPending(prompt, mismatch);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!prompt->shown)
|
||||||
|
recoveryShowPrompt(prompt, mismatch, g_state.fallback != NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static MsgBoxHandle showSpiceInputHelp(void)
|
static MsgBoxHandle showSpiceInputHelp(void)
|
||||||
@@ -2412,6 +2972,9 @@ static int lg_run(void)
|
|||||||
}
|
}
|
||||||
DEBUG_INFO("Using Transport: %s", g_state.transport.ops->name);
|
DEBUG_INFO("Using Transport: %s", g_state.transport.ops->name);
|
||||||
|
|
||||||
|
LG_RecoveryInfo initialRecovery = { 0 };
|
||||||
|
const bool initialRecoveryValid = recoveryGetInfo(&initialRecovery);
|
||||||
|
|
||||||
g_state.videoOps =
|
g_state.videoOps =
|
||||||
g_state.transport.ops->getVideoOps(g_state.transport.handle);
|
g_state.transport.ops->getVideoOps(g_state.transport.handle);
|
||||||
if (!g_state.videoOps ||
|
if (!g_state.videoOps ||
|
||||||
@@ -2460,7 +3023,9 @@ static int lg_run(void)
|
|||||||
|
|
||||||
g_state.micDefaultState = g_params.micDefaultState;
|
g_state.micDefaultState = g_params.micDefaultState;
|
||||||
|
|
||||||
if (!fallbackStart())
|
const uint8_t * fallbackUUID = initialRecoveryValid &&
|
||||||
|
initialRecovery.uuidValid ? initialRecovery.uuid : NULL;
|
||||||
|
if (!fallbackStart(fallbackUUID))
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
// select and init a renderer
|
// select and init a renderer
|
||||||
@@ -2621,6 +3186,11 @@ static int lg_run(void)
|
|||||||
LG_TransportSession session;
|
LG_TransportSession session;
|
||||||
MsgBoxHandle msgs[10];
|
MsgBoxHandle msgs[10];
|
||||||
int msgsCount;
|
int msgsCount;
|
||||||
|
struct RecoveryPrompt recoveryPrompt = { 0 };
|
||||||
|
atomic_init(&recoveryPrompt.choice, RECOVERY_PENDING);
|
||||||
|
atomic_init(&recoveryPrompt.handle, 0);
|
||||||
|
atomic_init(&recoveryPrompt.message, 0);
|
||||||
|
atomic_init(&recoveryPrompt.messageClosed, false);
|
||||||
|
|
||||||
restart:
|
restart:
|
||||||
frameTimingReset();
|
frameTimingReset();
|
||||||
@@ -2636,7 +3206,7 @@ restart:
|
|||||||
|
|
||||||
if (initialFallbackEnable && microtime() > initialFallbackEnable)
|
if (initialFallbackEnable && microtime() > initialFallbackEnable)
|
||||||
{
|
{
|
||||||
app_useVideoSource(LG_VIDEO_SOURCE_FALLBACK);
|
if (fallbackRequestVideo())
|
||||||
initialFallbackEnable = 0;
|
initialFallbackEnable = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2650,7 +3220,7 @@ restart:
|
|||||||
&probeThread))
|
&probeThread))
|
||||||
{
|
{
|
||||||
DEBUG_ERROR("Failed to create transport session probe thread");
|
DEBUG_ERROR("Failed to create transport session probe thread");
|
||||||
return -1;
|
return recoveryExit(&recoveryPrompt, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (app_getState() == APP_STATE_RUNNING &&
|
while (app_getState() == APP_STATE_RUNNING &&
|
||||||
@@ -2663,11 +3233,11 @@ restart:
|
|||||||
if (!lgJoinThread(probeThread, NULL))
|
if (!lgJoinThread(probeThread, NULL))
|
||||||
{
|
{
|
||||||
DEBUG_ERROR("Failed to join transport session probe thread");
|
DEBUG_ERROR("Failed to join transport session probe thread");
|
||||||
return -1;
|
return recoveryExit(&recoveryPrompt, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (app_getState() != APP_STATE_RUNNING)
|
if (app_getState() != APP_STATE_RUNNING)
|
||||||
return -1;
|
return recoveryExit(&recoveryPrompt, -1);
|
||||||
|
|
||||||
if (probe.status == LG_TRANSPORT_OK)
|
if (probe.status == LG_TRANSPORT_OK)
|
||||||
{
|
{
|
||||||
@@ -2679,14 +3249,10 @@ restart:
|
|||||||
if (probe.status == LG_TRANSPORT_INVALID_VERSION)
|
if (probe.status == LG_TRANSPORT_INVALID_VERSION)
|
||||||
{
|
{
|
||||||
if (waitCount++ == 0)
|
if (waitCount++ == 0)
|
||||||
{
|
reportBadVersion(&probe.session.versionMismatch);
|
||||||
reportBadVersion();
|
|
||||||
msgs[msgsCount++] = app_msgBox(
|
recoveryHandleMismatch(
|
||||||
"Incompatible Transport Version",
|
&recoveryPrompt, &probe.session.versionMismatch);
|
||||||
"The selected transport source is not compatible with this client.\n"
|
|
||||||
"Please install matching versions.");
|
|
||||||
DEBUG_INFO("Remote transport version: %u", probe.session.remoteVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_state.ds->wait(1000);
|
g_state.ds->wait(1000);
|
||||||
continue;
|
continue;
|
||||||
@@ -2703,22 +3269,50 @@ restart:
|
|||||||
}
|
}
|
||||||
if (waitCount == 30 && !g_params.disableWaitingMessage)
|
if (waitCount == 30 && !g_params.disableWaitingMessage)
|
||||||
{
|
{
|
||||||
msgs[msgsCount++] = app_msgBox(
|
const size_t msgCapacity = sizeof(msgs) / sizeof(*msgs);
|
||||||
"Transport Source Not Available",
|
retainMessage(msgs, msgCapacity, &msgsCount,
|
||||||
|
app_msgBox("Transport Source Not Available",
|
||||||
"The selected transport source is not available.\n"
|
"The selected transport source is not available.\n"
|
||||||
"Continuing to wait...");
|
"Continuing to wait..."));
|
||||||
msgs[msgsCount++] = showSpiceInputHelp();
|
retainMessage(msgs, msgCapacity, &msgsCount,
|
||||||
|
showSpiceInputHelp());
|
||||||
}
|
}
|
||||||
g_state.ds->wait(1000);
|
g_state.ds->wait(1000);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
DEBUG_ERROR("Transport connection failed with status %d", probe.status);
|
DEBUG_ERROR("Transport connection failed with status %d", probe.status);
|
||||||
return -1;
|
return recoveryExit(&recoveryPrompt, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (app_getState() != APP_STATE_RUNNING)
|
if (app_getState() != APP_STATE_RUNNING)
|
||||||
return -1;
|
return recoveryExit(&recoveryPrompt, -1);
|
||||||
|
|
||||||
|
recoveryClose(&recoveryPrompt);
|
||||||
|
LG_RecoveryInfo recoveryInfo = { 0 };
|
||||||
|
if (recoveryGetInfo(&recoveryInfo) &&
|
||||||
|
(recoveryInfo.state == LG_RECOVERY_STATE_ACTIVE ||
|
||||||
|
recoveryInfo.state == LG_RECOVERY_STATE_SWITCHING ||
|
||||||
|
recoveryInfo.request == LG_RECOVERY_REQ_RECOVERY ||
|
||||||
|
recoveryInfo.ackRequest == LG_RECOVERY_REQ_RECOVERY) &&
|
||||||
|
g_state.transport.ops->requestRecovery)
|
||||||
|
{
|
||||||
|
const LG_TransportStatus status =
|
||||||
|
g_state.transport.ops->requestRecovery(g_state.transport.handle,
|
||||||
|
LG_RECOVERY_REQ_NORMAL, NULL);
|
||||||
|
if (status != LG_TRANSPORT_OK)
|
||||||
|
DEBUG_WARN("Failed to leave recovery mode: %d", status);
|
||||||
|
}
|
||||||
|
recoveryPrompt.serial = 0;
|
||||||
|
recoveryPrompt.failedSerial = 0;
|
||||||
|
recoveryPrompt.reportedState = LG_RECOVERY_STATE_UNKNOWN;
|
||||||
|
recoveryPrompt.shown = false;
|
||||||
|
recoveryPrompt.requested = false;
|
||||||
|
recoveryPrompt.owned = false;
|
||||||
|
recoveryPrompt.retryPrompt = false;
|
||||||
|
recoveryPrompt.retryDeclined = false;
|
||||||
|
atomic_store_explicit(
|
||||||
|
&recoveryPrompt.choice, RECOVERY_PENDING, memory_order_relaxed);
|
||||||
|
|
||||||
waitCount = 100;
|
waitCount = 100;
|
||||||
for (int i = 0; i < msgsCount; ++i)
|
for (int i = 0; i < msgsCount; ++i)
|
||||||
@@ -2811,7 +3405,7 @@ restart:
|
|||||||
{
|
{
|
||||||
videoSourceBegin(LG_VIDEO_SOURCE_PRIMARY);
|
videoSourceBegin(LG_VIDEO_SOURCE_PRIMARY);
|
||||||
if (!core_startCursorThread() || !core_startFrameThread())
|
if (!core_startCursorThread() || !core_startFrameThread())
|
||||||
return -1;
|
return recoveryExit(&recoveryPrompt, -1);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -2819,7 +3413,7 @@ restart:
|
|||||||
g_state.transport.handle, true))
|
g_state.transport.handle, true))
|
||||||
{
|
{
|
||||||
DEBUG_ERROR("Failed to activate the primary software surface");
|
DEBUG_ERROR("Failed to activate the primary software surface");
|
||||||
return -1;
|
return recoveryExit(&recoveryPrompt, -1);
|
||||||
}
|
}
|
||||||
app_useVideoSource(LG_VIDEO_SOURCE_PRIMARY);
|
app_useVideoSource(LG_VIDEO_SOURCE_PRIMARY);
|
||||||
}
|
}
|
||||||
@@ -2852,7 +3446,6 @@ restart:
|
|||||||
atomic_store_explicit(
|
atomic_store_explicit(
|
||||||
&g_state.lgHostConnected, false, memory_order_release);
|
&g_state.lgHostConnected, false, memory_order_release);
|
||||||
g_state.guestUUIDValid = false;
|
g_state.guestUUIDValid = false;
|
||||||
lgTransportFallback_clearPrimaryUUID(g_state.fallback);
|
|
||||||
|
|
||||||
lgSignalEvent(e_startup);
|
lgSignalEvent(e_startup);
|
||||||
lgSignalEvent(g_state.frameEvent);
|
lgSignalEvent(g_state.frameEvent);
|
||||||
@@ -2877,7 +3470,7 @@ restart:
|
|||||||
goto restart;
|
goto restart;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return recoveryExit(&recoveryPrompt, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void lg_shutdown(void)
|
static void lg_shutdown(void)
|
||||||
|
|||||||
@@ -37,7 +37,9 @@ struct Msg
|
|||||||
char * message;
|
char * message;
|
||||||
StringList lines;
|
StringList lines;
|
||||||
MsgBoxConfirmCallback confirm;
|
MsgBoxConfirmCallback confirm;
|
||||||
void * opaque;
|
void * confirmOpaque;
|
||||||
|
MsgBoxCloseCallback close;
|
||||||
|
void * closeOpaque;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MsgState
|
struct MsgState
|
||||||
@@ -62,6 +64,9 @@ static bool msg_init(void ** udata, const void * params)
|
|||||||
|
|
||||||
static void freeMsg(struct Msg * msg)
|
static void freeMsg(struct Msg * msg)
|
||||||
{
|
{
|
||||||
|
if (msg->close)
|
||||||
|
msg->close((MsgBoxHandle)msg, msg->closeOpaque);
|
||||||
|
|
||||||
free(msg->caption);
|
free(msg->caption);
|
||||||
free(msg->message);
|
free(msg->message);
|
||||||
stringlist_free(&msg->lines);
|
stringlist_free(&msg->lines);
|
||||||
@@ -144,14 +149,14 @@ static int msg_render(void * udata, bool interactive, struct Rect * windowRects,
|
|||||||
if (igButton("Yes", textSize))
|
if (igButton("Yes", textSize))
|
||||||
{
|
{
|
||||||
destroy = true;
|
destroy = true;
|
||||||
msg->confirm(true, msg->opaque);
|
msg->confirm(true, msg->confirmOpaque);
|
||||||
}
|
}
|
||||||
|
|
||||||
igSameLine(0.0f, -1.0f);
|
igSameLine(0.0f, -1.0f);
|
||||||
if (igButton("No", textSize))
|
if (igButton("No", textSize))
|
||||||
{
|
{
|
||||||
destroy = true;
|
destroy = true;
|
||||||
msg->confirm(false, msg->opaque);
|
msg->confirm(false, msg->confirmOpaque);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -202,7 +207,9 @@ bool overlayMsg_modal(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
MsgBoxHandle overlayMsg_show(
|
MsgBoxHandle overlayMsg_show(
|
||||||
const char * caption, MsgBoxConfirmCallback confirm, void * opaque,
|
const char * caption,
|
||||||
|
MsgBoxConfirmCallback confirm, void * confirmOpaque,
|
||||||
|
MsgBoxCloseCallback close, void * closeOpaque,
|
||||||
const char * fmt, va_list args)
|
const char * fmt, va_list args)
|
||||||
{
|
{
|
||||||
struct Msg * msg = calloc(1, sizeof(*msg));
|
struct Msg * msg = calloc(1, sizeof(*msg));
|
||||||
@@ -228,8 +235,6 @@ MsgBoxHandle overlayMsg_show(
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
msg->confirm = confirm;
|
|
||||||
msg->opaque = opaque;
|
|
||||||
if (valloc_sprintf(&msg->message, fmt ? fmt : "", args) < 0)
|
if (valloc_sprintf(&msg->message, fmt ? fmt : "", args) < 0)
|
||||||
{
|
{
|
||||||
DEBUG_ERROR("failed to format message");
|
DEBUG_ERROR("failed to format message");
|
||||||
@@ -256,6 +261,10 @@ MsgBoxHandle overlayMsg_show(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
msg->confirm = confirm;
|
||||||
|
msg->confirmOpaque = confirmOpaque;
|
||||||
|
msg->close = close;
|
||||||
|
msg->closeOpaque = closeOpaque;
|
||||||
ll_push(l_msg.messages, msg);
|
ll_push(l_msg.messages, msg);
|
||||||
app_invalidateOverlay(false);
|
app_invalidateOverlay(false);
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,9 @@
|
|||||||
bool overlayMsg_modal(void);
|
bool overlayMsg_modal(void);
|
||||||
|
|
||||||
MsgBoxHandle overlayMsg_show(
|
MsgBoxHandle overlayMsg_show(
|
||||||
const char * caption, MsgBoxConfirmCallback confirm, void * opaque,
|
const char * caption,
|
||||||
|
MsgBoxConfirmCallback confirm, void * confirmOpaque,
|
||||||
|
MsgBoxCloseCallback close, void * closeOpaque,
|
||||||
const char * fmt, va_list args);
|
const char * fmt, va_list args);
|
||||||
|
|
||||||
void overlayMsg_close(MsgBoxHandle handle);
|
void overlayMsg_close(MsgBoxHandle handle);
|
||||||
|
|||||||
@@ -50,9 +50,12 @@ struct LG_TransportFallback
|
|||||||
LGThread * thread;
|
LGThread * thread;
|
||||||
LGEvent * wakeEvent;
|
LGEvent * wakeEvent;
|
||||||
LG_RWLock lock;
|
LG_RWLock lock;
|
||||||
|
LG_Lock providerLock;
|
||||||
|
LG_Lock eventLock;
|
||||||
|
|
||||||
atomic_bool stop;
|
atomic_bool stop;
|
||||||
atomic_bool ready;
|
atomic_bool ready;
|
||||||
|
atomic_bool admitted;
|
||||||
|
|
||||||
LG_TransportInstance transport;
|
LG_TransportInstance transport;
|
||||||
LG_TransportSession session;
|
LG_TransportSession session;
|
||||||
@@ -62,6 +65,7 @@ struct LG_TransportFallback
|
|||||||
bool connected;
|
bool connected;
|
||||||
bool providersPublished;
|
bool providersPublished;
|
||||||
bool connectedReported;
|
bool connectedReported;
|
||||||
|
bool disconnectedReported;
|
||||||
bool closing;
|
bool closing;
|
||||||
bool videoRequested;
|
bool videoRequested;
|
||||||
bool videoActive;
|
bool videoActive;
|
||||||
@@ -89,6 +93,11 @@ static bool uuidMismatchLocked(const LG_TransportFallback * fallback)
|
|||||||
sizeof(fallback->primaryUUID)) != 0;
|
sizeof(fallback->primaryUUID)) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool uuidAdmittedLocked(const LG_TransportFallback * fallback)
|
||||||
|
{
|
||||||
|
return !uuidMismatchLocked(fallback);
|
||||||
|
}
|
||||||
|
|
||||||
static bool recordMismatchLocked(LG_TransportFallback * fallback,
|
static bool recordMismatchLocked(LG_TransportFallback * fallback,
|
||||||
uint8_t primary[16], uint8_t remote[16])
|
uint8_t primary[16], uint8_t remote[16])
|
||||||
{
|
{
|
||||||
@@ -119,10 +128,50 @@ static bool sessionLive(const LG_TransportFallback * fallback)
|
|||||||
fallback->transport.ops->sessionValid(fallback->transport.handle);
|
fallback->transport.ops->sessionValid(fallback->transport.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void notifyConnected(LG_TransportFallback * fallback)
|
||||||
|
{
|
||||||
|
LG_LOCK(fallback->eventLock);
|
||||||
|
LG_LOCK_EXCLUSIVE(fallback->lock);
|
||||||
|
const bool report =
|
||||||
|
atomic_load_explicit(&fallback->ready, memory_order_acquire) &&
|
||||||
|
atomic_load_explicit(&fallback->admitted, memory_order_acquire) &&
|
||||||
|
uuidAdmittedLocked(fallback) && !fallback->closing &&
|
||||||
|
!fallback->disconnectedReported;
|
||||||
|
fallback->connectedReported = report;
|
||||||
|
const LG_TransportSession session = fallback->session;
|
||||||
|
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||||
|
|
||||||
|
if (report && fallback->eventOps.connected)
|
||||||
|
fallback->eventOps.connected(fallback->eventOpaque, &session);
|
||||||
|
LG_UNLOCK(fallback->eventLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void notifyDisconnected(
|
||||||
|
LG_TransportFallback * fallback, bool requested)
|
||||||
|
{
|
||||||
|
if (!requested)
|
||||||
|
return;
|
||||||
|
|
||||||
|
LG_LOCK(fallback->eventLock);
|
||||||
|
LG_LOCK_EXCLUSIVE(fallback->lock);
|
||||||
|
const bool report = !fallback->disconnectedReported;
|
||||||
|
if (report)
|
||||||
|
fallback->disconnectedReported = true;
|
||||||
|
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||||
|
|
||||||
|
if (report && fallback->eventOps.disconnected)
|
||||||
|
fallback->eventOps.disconnected(fallback->eventOpaque);
|
||||||
|
LG_UNLOCK(fallback->eventLock);
|
||||||
|
}
|
||||||
|
|
||||||
static void unpublishProviders(LG_TransportFallback * fallback, bool live)
|
static void unpublishProviders(LG_TransportFallback * fallback, bool live)
|
||||||
{
|
{
|
||||||
|
LG_LOCK(fallback->providerLock);
|
||||||
if (!fallback->providersPublished)
|
if (!fallback->providersPublished)
|
||||||
|
{
|
||||||
|
LG_UNLOCK(fallback->providerLock);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (live)
|
if (live)
|
||||||
{
|
{
|
||||||
@@ -137,12 +186,14 @@ static void unpublishProviders(LG_TransportFallback * fallback, bool live)
|
|||||||
lgClipboard_dropFallback();
|
lgClipboard_dropFallback();
|
||||||
}
|
}
|
||||||
fallback->providersPublished = false;
|
fallback->providersPublished = false;
|
||||||
|
LG_UNLOCK(fallback->providerLock);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void closeVideoAdmission(LG_TransportFallback * fallback)
|
static void closeVideoAdmission(LG_TransportFallback * fallback)
|
||||||
{
|
{
|
||||||
LG_LOCK_EXCLUSIVE(fallback->lock);
|
LG_LOCK_EXCLUSIVE(fallback->lock);
|
||||||
atomic_store_explicit(&fallback->ready, false, memory_order_release);
|
atomic_store_explicit(&fallback->ready, false, memory_order_release);
|
||||||
|
atomic_store_explicit(&fallback->admitted, false, memory_order_release);
|
||||||
fallback->closing = true;
|
fallback->closing = true;
|
||||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||||
}
|
}
|
||||||
@@ -184,8 +235,20 @@ static bool cleanupConnection(LG_TransportFallback * fallback,
|
|||||||
return reportDisconnected;
|
return reportDisconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void publishProviders(LG_TransportFallback * fallback)
|
static bool publishProviders(LG_TransportFallback * fallback)
|
||||||
{
|
{
|
||||||
|
LG_LOCK(fallback->providerLock);
|
||||||
|
LG_LOCK_SHARED(fallback->lock);
|
||||||
|
const bool admitted = atomic_load_explicit(
|
||||||
|
&fallback->admitted, memory_order_acquire) &&
|
||||||
|
uuidAdmittedLocked(fallback) && !fallback->closing;
|
||||||
|
if (!admitted)
|
||||||
|
{
|
||||||
|
LG_UNLOCK_SHARED(fallback->lock);
|
||||||
|
LG_UNLOCK(fallback->providerLock);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void * inputOpaque = NULL;
|
void * inputOpaque = NULL;
|
||||||
const LG_InputOps * inputOps = fallback->transport.ops->getInputOps ?
|
const LG_InputOps * inputOps = fallback->transport.ops->getInputOps ?
|
||||||
fallback->transport.ops->getInputOps(
|
fallback->transport.ops->getInputOps(
|
||||||
@@ -205,9 +268,12 @@ static void publishProviders(LG_TransportFallback * fallback)
|
|||||||
fallback->transport.handle, &clipboardOpaque) : NULL;
|
fallback->transport.handle, &clipboardOpaque) : NULL;
|
||||||
lgClipboard_setFallback(clipboardOps, clipboardOpaque);
|
lgClipboard_setFallback(clipboardOps, clipboardOpaque);
|
||||||
fallback->providersPublished = true;
|
fallback->providersPublished = true;
|
||||||
|
LG_UNLOCK_SHARED(fallback->lock);
|
||||||
|
LG_UNLOCK(fallback->providerLock);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool publishConnection(LG_TransportFallback * fallback,
|
static bool admitConnection(LG_TransportFallback * fallback,
|
||||||
bool * reportMismatch, uint8_t primaryUUID[16],
|
bool * reportMismatch, uint8_t primaryUUID[16],
|
||||||
uint8_t fallbackUUID[16])
|
uint8_t fallbackUUID[16])
|
||||||
{
|
{
|
||||||
@@ -218,11 +284,24 @@ static bool publishConnection(LG_TransportFallback * fallback,
|
|||||||
fallback, primaryUUID, fallbackUUID);
|
fallback, primaryUUID, fallbackUUID);
|
||||||
const bool stop = atomic_load_explicit(
|
const bool stop = atomic_load_explicit(
|
||||||
&fallback->stop, memory_order_acquire);
|
&fallback->stop, memory_order_acquire);
|
||||||
|
const bool admitted = !reject && !stop && !fallback->closing;
|
||||||
|
if (admitted)
|
||||||
|
atomic_store_explicit(
|
||||||
|
&fallback->admitted, true, memory_order_release);
|
||||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||||
if (reject || stop)
|
return admitted;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool publishConnection(LG_TransportFallback * fallback,
|
||||||
|
bool * reportMismatch, uint8_t primaryUUID[16],
|
||||||
|
uint8_t fallbackUUID[16])
|
||||||
|
{
|
||||||
|
if (!admitConnection(fallback, reportMismatch,
|
||||||
|
primaryUUID, fallbackUUID))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
publishProviders(fallback);
|
if (!publishProviders(fallback))
|
||||||
|
return false;
|
||||||
|
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
@@ -231,12 +310,14 @@ static bool publishConnection(LG_TransportFallback * fallback,
|
|||||||
if (reject)
|
if (reject)
|
||||||
*reportMismatch = recordMismatchLocked(
|
*reportMismatch = recordMismatchLocked(
|
||||||
fallback, primaryUUID, fallbackUUID);
|
fallback, primaryUUID, fallbackUUID);
|
||||||
|
const bool admitted = uuidAdmittedLocked(fallback);
|
||||||
const bool stop = atomic_load_explicit(
|
const bool stop = atomic_load_explicit(
|
||||||
&fallback->stop, memory_order_acquire);
|
&fallback->stop, memory_order_acquire);
|
||||||
|
const bool closing = fallback->closing;
|
||||||
const bool requested = fallback->videoRequested;
|
const bool requested = fallback->videoRequested;
|
||||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||||
|
|
||||||
if (reject || stop)
|
if (reject || !admitted || stop || closing)
|
||||||
return false;
|
return false;
|
||||||
if (requested == fallback->videoActive)
|
if (requested == fallback->videoActive)
|
||||||
break;
|
break;
|
||||||
@@ -252,15 +333,18 @@ static bool publishConnection(LG_TransportFallback * fallback,
|
|||||||
if (finalReject)
|
if (finalReject)
|
||||||
*reportMismatch = recordMismatchLocked(
|
*reportMismatch = recordMismatchLocked(
|
||||||
fallback, primaryUUID, fallbackUUID);
|
fallback, primaryUUID, fallbackUUID);
|
||||||
|
const bool finalAdmitted = uuidAdmittedLocked(fallback);
|
||||||
const bool finalStop = atomic_load_explicit(
|
const bool finalStop = atomic_load_explicit(
|
||||||
&fallback->stop, memory_order_acquire);
|
&fallback->stop, memory_order_acquire);
|
||||||
if (!finalReject && !finalStop)
|
if (!finalReject && finalAdmitted && !finalStop && !fallback->closing)
|
||||||
{
|
{
|
||||||
fallback->mismatchReported = false;
|
fallback->mismatchReported = false;
|
||||||
atomic_store_explicit(&fallback->ready, true, memory_order_release);
|
atomic_store_explicit(&fallback->ready, true, memory_order_release);
|
||||||
}
|
}
|
||||||
|
const bool published = !finalReject && finalAdmitted && !finalStop &&
|
||||||
|
!fallback->closing;
|
||||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||||
return !finalReject && !finalStop;
|
return published;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool connectFallback(LG_TransportFallback * fallback)
|
static bool connectFallback(LG_TransportFallback * fallback)
|
||||||
@@ -317,6 +401,7 @@ static bool connectFallback(LG_TransportFallback * fallback)
|
|||||||
LG_LOCK_EXCLUSIVE(fallback->lock);
|
LG_LOCK_EXCLUSIVE(fallback->lock);
|
||||||
fallback->connected = true;
|
fallback->connected = true;
|
||||||
fallback->session = session;
|
fallback->session = session;
|
||||||
|
fallback->disconnectedReported = false;
|
||||||
++fallback->connectionSerial;
|
++fallback->connectionSerial;
|
||||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||||
|
|
||||||
@@ -333,13 +418,7 @@ static bool connectFallback(LG_TransportFallback * fallback)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
LG_LOCK_EXCLUSIVE(fallback->lock);
|
notifyConnected(fallback);
|
||||||
fallback->connectedReported = true;
|
|
||||||
const LG_TransportSession reportedSession = fallback->session;
|
|
||||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
|
||||||
if (fallback->eventOps.connected)
|
|
||||||
fallback->eventOps.connected(
|
|
||||||
fallback->eventOpaque, &reportedSession);
|
|
||||||
|
|
||||||
while (!atomic_load_explicit(&fallback->stop, memory_order_acquire))
|
while (!atomic_load_explicit(&fallback->stop, memory_order_acquire))
|
||||||
{
|
{
|
||||||
@@ -351,8 +430,10 @@ static bool connectFallback(LG_TransportFallback * fallback)
|
|||||||
if (reject)
|
if (reject)
|
||||||
reportMismatch = recordMismatchLocked(
|
reportMismatch = recordMismatchLocked(
|
||||||
fallback, primaryUUID, fallbackUUID);
|
fallback, primaryUUID, fallbackUUID);
|
||||||
|
const bool admitted = uuidAdmittedLocked(fallback);
|
||||||
|
const bool closing = fallback->closing;
|
||||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||||
if (reject)
|
if (reject || !admitted || closing)
|
||||||
break;
|
break;
|
||||||
if (!sessionLive(fallback))
|
if (!sessionLive(fallback))
|
||||||
break;
|
break;
|
||||||
@@ -365,8 +446,7 @@ static bool connectFallback(LG_TransportFallback * fallback)
|
|||||||
if (reportMismatch && fallback->eventOps.uuidMismatch)
|
if (reportMismatch && fallback->eventOps.uuidMismatch)
|
||||||
fallback->eventOps.uuidMismatch(
|
fallback->eventOps.uuidMismatch(
|
||||||
fallback->eventOpaque, primaryUUID, fallbackUUID);
|
fallback->eventOpaque, primaryUUID, fallbackUUID);
|
||||||
if (reportDisconnect && fallback->eventOps.disconnected)
|
notifyDisconnected(fallback, reportDisconnect);
|
||||||
fallback->eventOps.disconnected(fallback->eventOpaque);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,14 +468,14 @@ static int fallbackThread(void * opaque)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const bool reportDisconnect = cleanupConnection(fallback, false);
|
const bool reportDisconnect = cleanupConnection(fallback, false);
|
||||||
if (reportDisconnect && fallback->eventOps.disconnected)
|
notifyDisconnected(fallback, reportDisconnect);
|
||||||
fallback->eventOps.disconnected(fallback->eventOpaque);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool lgTransportFallback_start(const char * transportName,
|
bool lgTransportFallback_start(const char * transportName,
|
||||||
const LG_SwSurfaceEventOps * surfaceEvents, void * surfaceOpaque,
|
const LG_SwSurfaceEventOps * surfaceEvents, void * surfaceOpaque,
|
||||||
const LG_TransportFallbackEventOps * eventOps, void * eventOpaque,
|
const LG_TransportFallbackEventOps * eventOps, void * eventOpaque,
|
||||||
|
const uint8_t primaryUUID[16],
|
||||||
LG_TransportFallback ** result)
|
LG_TransportFallback ** result)
|
||||||
{
|
{
|
||||||
if (!result)
|
if (!result)
|
||||||
@@ -411,8 +491,11 @@ bool lgTransportFallback_start(const char * transportName,
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
LG_RWLOCK_INIT(fallback->lock);
|
LG_RWLOCK_INIT(fallback->lock);
|
||||||
|
LG_LOCK_INIT(fallback->providerLock);
|
||||||
|
LG_LOCK_INIT(fallback->eventLock);
|
||||||
atomic_init(&fallback->stop, false);
|
atomic_init(&fallback->stop, false);
|
||||||
atomic_init(&fallback->ready, false);
|
atomic_init(&fallback->ready, false);
|
||||||
|
atomic_init(&fallback->admitted, false);
|
||||||
|
|
||||||
fallback->transportName = strdup(transportName);
|
fallback->transportName = strdup(transportName);
|
||||||
if (!fallback->transportName)
|
if (!fallback->transportName)
|
||||||
@@ -423,6 +506,12 @@ bool lgTransportFallback_start(const char * transportName,
|
|||||||
if (eventOps)
|
if (eventOps)
|
||||||
fallback->eventOps = *eventOps;
|
fallback->eventOps = *eventOps;
|
||||||
fallback->eventOpaque = eventOpaque;
|
fallback->eventOpaque = eventOpaque;
|
||||||
|
if (primaryUUID)
|
||||||
|
{
|
||||||
|
memcpy(fallback->primaryUUID, primaryUUID,
|
||||||
|
sizeof(fallback->primaryUUID));
|
||||||
|
fallback->primaryUUIDValid = true;
|
||||||
|
}
|
||||||
|
|
||||||
fallback->wakeEvent = lgCreateEvent(true, 0);
|
fallback->wakeEvent = lgCreateEvent(true, 0);
|
||||||
if (!fallback->wakeEvent)
|
if (!fallback->wakeEvent)
|
||||||
@@ -439,6 +528,8 @@ fail:
|
|||||||
*result = NULL;
|
*result = NULL;
|
||||||
if (fallback->wakeEvent)
|
if (fallback->wakeEvent)
|
||||||
lgFreeEvent(fallback->wakeEvent);
|
lgFreeEvent(fallback->wakeEvent);
|
||||||
|
LG_LOCK_FREE(fallback->eventLock);
|
||||||
|
LG_LOCK_FREE(fallback->providerLock);
|
||||||
LG_RWLOCK_FREE(fallback->lock);
|
LG_RWLOCK_FREE(fallback->lock);
|
||||||
free(fallback->transportName);
|
free(fallback->transportName);
|
||||||
free(fallback);
|
free(fallback);
|
||||||
@@ -465,6 +556,8 @@ void lgTransportFallback_stop(LG_TransportFallback ** fallbackPtr)
|
|||||||
}
|
}
|
||||||
|
|
||||||
lgFreeEvent(fallback->wakeEvent);
|
lgFreeEvent(fallback->wakeEvent);
|
||||||
|
LG_LOCK_FREE(fallback->eventLock);
|
||||||
|
LG_LOCK_FREE(fallback->providerLock);
|
||||||
LG_RWLOCK_FREE(fallback->lock);
|
LG_RWLOCK_FREE(fallback->lock);
|
||||||
free(fallback->transportName);
|
free(fallback->transportName);
|
||||||
free(fallback);
|
free(fallback);
|
||||||
@@ -476,6 +569,24 @@ bool lgTransportFallback_ready(const LG_TransportFallback * fallback)
|
|||||||
&fallback->ready, memory_order_acquire);
|
&fallback->ready, memory_order_acquire);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool lgTransportFallback_admitted(const LG_TransportFallback * fallback)
|
||||||
|
{
|
||||||
|
return fallback && atomic_load_explicit(
|
||||||
|
&fallback->admitted, memory_order_acquire);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool lgTransportFallback_videoRequested(
|
||||||
|
LG_TransportFallback * fallback)
|
||||||
|
{
|
||||||
|
if (!fallback)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
LG_LOCK_SHARED(fallback->lock);
|
||||||
|
const bool requested = fallback->videoRequested;
|
||||||
|
LG_UNLOCK_SHARED(fallback->lock);
|
||||||
|
return requested;
|
||||||
|
}
|
||||||
|
|
||||||
static bool applyVideoRequest(LG_TransportFallback * fallback)
|
static bool applyVideoRequest(LG_TransportFallback * fallback)
|
||||||
{
|
{
|
||||||
for (;;)
|
for (;;)
|
||||||
@@ -533,12 +644,30 @@ void lgTransportFallback_requestVideoActive(
|
|||||||
lgSignalEvent(fallback->wakeEvent);
|
lgSignalEvent(fallback->wakeEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool revokeAdmissionLocked(LG_TransportFallback * fallback)
|
||||||
|
{
|
||||||
|
atomic_store_explicit(&fallback->ready, false, memory_order_release);
|
||||||
|
atomic_store_explicit(&fallback->admitted, false, memory_order_release);
|
||||||
|
if (!fallback->connected)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
fallback->connectedReported = false;
|
||||||
|
fallback->closing = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void lgTransportFallback_setPrimaryUUID(
|
void lgTransportFallback_setPrimaryUUID(
|
||||||
LG_TransportFallback * fallback, const uint8_t uuid[16])
|
LG_TransportFallback * fallback, const uint8_t uuid[16])
|
||||||
{
|
{
|
||||||
if (!fallback || !uuid)
|
if (!fallback || !uuid)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
bool revoked = false;
|
||||||
|
bool notifyDisconnect = false;
|
||||||
|
bool reportMismatch = false;
|
||||||
|
uint8_t primaryUUID[16];
|
||||||
|
uint8_t fallbackUUID[16];
|
||||||
|
|
||||||
LG_LOCK_EXCLUSIVE(fallback->lock);
|
LG_LOCK_EXCLUSIVE(fallback->lock);
|
||||||
const bool changed = !fallback->primaryUUIDValid ||
|
const bool changed = !fallback->primaryUUIDValid ||
|
||||||
memcmp(fallback->primaryUUID, uuid,
|
memcmp(fallback->primaryUUID, uuid,
|
||||||
@@ -547,11 +676,24 @@ void lgTransportFallback_setPrimaryUUID(
|
|||||||
{
|
{
|
||||||
memcpy(fallback->primaryUUID, uuid, sizeof(fallback->primaryUUID));
|
memcpy(fallback->primaryUUID, uuid, sizeof(fallback->primaryUUID));
|
||||||
fallback->primaryUUIDValid = true;
|
fallback->primaryUUIDValid = true;
|
||||||
|
if (uuidMismatchLocked(fallback))
|
||||||
|
{
|
||||||
|
revoked = true;
|
||||||
|
notifyDisconnect = revokeAdmissionLocked(fallback);
|
||||||
|
reportMismatch = recordMismatchLocked(
|
||||||
|
fallback, primaryUUID, fallbackUUID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||||
|
|
||||||
if (changed)
|
if (changed)
|
||||||
lgSignalEvent(fallback->wakeEvent);
|
lgSignalEvent(fallback->wakeEvent);
|
||||||
|
if (revoked)
|
||||||
|
unpublishProviders(fallback, false);
|
||||||
|
notifyDisconnected(fallback, notifyDisconnect);
|
||||||
|
if (reportMismatch && fallback->eventOps.uuidMismatch)
|
||||||
|
fallback->eventOps.uuidMismatch(
|
||||||
|
fallback->eventOpaque, primaryUUID, fallbackUUID);
|
||||||
}
|
}
|
||||||
|
|
||||||
void lgTransportFallback_clearPrimaryUUID(
|
void lgTransportFallback_clearPrimaryUUID(
|
||||||
@@ -562,7 +704,11 @@ void lgTransportFallback_clearPrimaryUUID(
|
|||||||
|
|
||||||
LG_LOCK_EXCLUSIVE(fallback->lock);
|
LG_LOCK_EXCLUSIVE(fallback->lock);
|
||||||
const bool changed = fallback->primaryUUIDValid;
|
const bool changed = fallback->primaryUUIDValid;
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
fallback->primaryUUIDValid = false;
|
fallback->primaryUUIDValid = false;
|
||||||
|
fallback->mismatchReported = false;
|
||||||
|
}
|
||||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||||
|
|
||||||
if (changed)
|
if (changed)
|
||||||
|
|||||||
@@ -41,10 +41,14 @@ LG_TransportFallbackEventOps;
|
|||||||
bool lgTransportFallback_start(const char * transportName,
|
bool lgTransportFallback_start(const char * transportName,
|
||||||
const LG_SwSurfaceEventOps * surfaceEvents, void * surfaceOpaque,
|
const LG_SwSurfaceEventOps * surfaceEvents, void * surfaceOpaque,
|
||||||
const LG_TransportFallbackEventOps * eventOps, void * eventOpaque,
|
const LG_TransportFallbackEventOps * eventOps, void * eventOpaque,
|
||||||
|
const uint8_t primaryUUID[16],
|
||||||
LG_TransportFallback ** result);
|
LG_TransportFallback ** result);
|
||||||
void lgTransportFallback_stop(LG_TransportFallback ** fallback);
|
void lgTransportFallback_stop(LG_TransportFallback ** fallback);
|
||||||
|
|
||||||
bool lgTransportFallback_ready(const LG_TransportFallback * fallback);
|
bool lgTransportFallback_ready(const LG_TransportFallback * fallback);
|
||||||
|
bool lgTransportFallback_admitted(const LG_TransportFallback * fallback);
|
||||||
|
bool lgTransportFallback_videoRequested(
|
||||||
|
LG_TransportFallback * fallback);
|
||||||
/* The requested state is retained across reconnects. */
|
/* The requested state is retained across reconnects. */
|
||||||
void lgTransportFallback_requestVideoActive(
|
void lgTransportFallback_requestVideoActive(
|
||||||
LG_TransportFallback * fallback, bool active);
|
LG_TransportFallback * fallback, bool active);
|
||||||
|
|||||||
@@ -23,12 +23,14 @@
|
|||||||
#include "input.h"
|
#include "input.h"
|
||||||
|
|
||||||
#include "common/KVMFR.h"
|
#include "common/KVMFR.h"
|
||||||
|
#include "common/KVMFRRecovery.h"
|
||||||
#include "common/LGMPConfig.h"
|
#include "common/LGMPConfig.h"
|
||||||
#include "common/debug.h"
|
#include "common/debug.h"
|
||||||
#include "common/ivshmem.h"
|
#include "common/ivshmem.h"
|
||||||
#include "common/locking.h"
|
#include "common/locking.h"
|
||||||
#include "common/option.h"
|
#include "common/option.h"
|
||||||
#include "common/stringutils.h"
|
#include "common/stringutils.h"
|
||||||
|
#include "common/time.h"
|
||||||
|
|
||||||
#include <lgmp/client.h>
|
#include <lgmp/client.h>
|
||||||
|
|
||||||
@@ -39,6 +41,11 @@
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#define LGMP_TIMING_SPIN_COUNT 4096
|
#define LGMP_TIMING_SPIN_COUNT 4096
|
||||||
|
#define LGMP_RECOVERY_PROBE_INTERVAL_US 10000U
|
||||||
|
#define LGMP_RECOVERY_PROBE_TIMEOUT_US \
|
||||||
|
((KVMFR_R_HEARTBEAT_MS * 3U) * 1000U)
|
||||||
|
#define LGMP_RECOVERY_LIVE_TIMEOUT_US \
|
||||||
|
((KVMFR_R_HEARTBEAT_MS * 4U) * 1000U)
|
||||||
|
|
||||||
struct DMAFrameInfo
|
struct DMAFrameInfo
|
||||||
{
|
{
|
||||||
@@ -91,6 +98,16 @@ struct LG_Transport
|
|||||||
struct DMAFrameInfo dma[LGMP_Q_FRAME_BUFFER_LEN];
|
struct DMAFrameInfo dma[LGMP_Q_FRAME_BUFFER_LEN];
|
||||||
uint8_t * pointerData;
|
uint8_t * pointerData;
|
||||||
size_t pointerDataSize;
|
size_t pointerDataSize;
|
||||||
|
|
||||||
|
size_t lgmpSize;
|
||||||
|
KVMFRR * recovery;
|
||||||
|
LG_Lock recoveryLock;
|
||||||
|
uint64_t recoveryCandidateSession;
|
||||||
|
uint64_t recoverySession;
|
||||||
|
uint64_t recoveryHeartbeatTime;
|
||||||
|
uint32_t recoveryCandidateHeartbeat;
|
||||||
|
uint32_t recoveryLGMPVersion;
|
||||||
|
bool recoveryLive;
|
||||||
};
|
};
|
||||||
|
|
||||||
static bool lgmp_deviceValidator(struct Option * opt, const char ** error)
|
static bool lgmp_deviceValidator(struct Option * opt, const char ** error)
|
||||||
@@ -186,6 +203,130 @@ static void lgmp_setup(void)
|
|||||||
option_register(options);
|
option_register(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool lgmp_recoveryMagic(const KVMFRR * recovery)
|
||||||
|
{
|
||||||
|
return recovery && memcmp(recovery->header.magic, KVMFR_R_MAGIC,
|
||||||
|
sizeof(recovery->header.magic)) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool lgmp_recoverySnapshot(const struct LG_Transport * this,
|
||||||
|
KVMFRRHeader * header, KVMFRRInfo * info)
|
||||||
|
{
|
||||||
|
const KVMFRR * recovery = this->recovery;
|
||||||
|
if (!recovery ||
|
||||||
|
__atomic_load_n(&recovery->header.ready, __ATOMIC_ACQUIRE) !=
|
||||||
|
KVMFR_R_READY)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
memcpy(header, &recovery->header, sizeof(*header));
|
||||||
|
memcpy(info, &recovery->info, sizeof(*info));
|
||||||
|
header->heartbeat = __atomic_load_n(&recovery->header.heartbeat,
|
||||||
|
__ATOMIC_ACQUIRE);
|
||||||
|
|
||||||
|
if (__atomic_load_n(&recovery->header.ready, __ATOMIC_ACQUIRE) !=
|
||||||
|
KVMFR_R_READY ||
|
||||||
|
memcmp(header->magic, KVMFR_R_MAGIC,
|
||||||
|
sizeof(header->magic)) != 0 ||
|
||||||
|
header->abiVersion != KVMFR_R_VERSION ||
|
||||||
|
header->structSize < sizeof(KVMFRR) ||
|
||||||
|
header->session == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool lgmp_refreshRecoveryLocked(struct LG_Transport * this, bool wait)
|
||||||
|
{
|
||||||
|
const uint64_t deadline = wait ?
|
||||||
|
microtime() + LGMP_RECOVERY_PROBE_TIMEOUT_US : 0;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
KVMFRRHeader header;
|
||||||
|
KVMFRRInfo info;
|
||||||
|
const bool valid = lgmp_recoverySnapshot(this, &header, &info);
|
||||||
|
const uint64_t now = microtime();
|
||||||
|
|
||||||
|
if (valid)
|
||||||
|
{
|
||||||
|
if (this->recoveryCandidateSession != header.session)
|
||||||
|
{
|
||||||
|
this->recoveryCandidateSession = header.session;
|
||||||
|
this->recoveryCandidateHeartbeat = header.heartbeat;
|
||||||
|
this->recoveryLive = false;
|
||||||
|
this->lgmpSize = this->shm.size;
|
||||||
|
}
|
||||||
|
else if (this->recoveryCandidateHeartbeat != header.heartbeat)
|
||||||
|
{
|
||||||
|
this->recoveryCandidateHeartbeat = header.heartbeat;
|
||||||
|
this->recoverySession = header.session;
|
||||||
|
this->recoveryHeartbeatTime = now;
|
||||||
|
this->recoveryLive = true;
|
||||||
|
this->lgmpSize = this->shm.size - KVMFR_R_REGION_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->recoveryLive && this->recoverySession == header.session &&
|
||||||
|
now - this->recoveryHeartbeatTime <=
|
||||||
|
LGMP_RECOVERY_LIVE_TIMEOUT_US)
|
||||||
|
{
|
||||||
|
this->recoveryLGMPVersion = header.lgmpVersion;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->recoveryLive = false;
|
||||||
|
this->lgmpSize = this->shm.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!wait || now >= deadline)
|
||||||
|
break;
|
||||||
|
usleep(LGMP_RECOVERY_PROBE_INTERVAL_US);
|
||||||
|
}
|
||||||
|
while (true);
|
||||||
|
|
||||||
|
this->recoveryLive = false;
|
||||||
|
this->lgmpSize = this->shm.size;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool lgmp_recoveryVersions(struct LG_Transport * this,
|
||||||
|
uint32_t * lgmpVersion, uint32_t * kvmfrVersion)
|
||||||
|
{
|
||||||
|
LG_LOCK(this->recoveryLock);
|
||||||
|
const bool live = lgmp_refreshRecoveryLocked(this,
|
||||||
|
lgmp_recoveryMagic(this->recovery));
|
||||||
|
KVMFRRHeader header;
|
||||||
|
KVMFRRInfo info;
|
||||||
|
const bool valid = live && lgmp_recoverySnapshot(this, &header, &info) &&
|
||||||
|
header.session == this->recoverySession;
|
||||||
|
if (valid)
|
||||||
|
{
|
||||||
|
if (lgmpVersion)
|
||||||
|
*lgmpVersion = header.lgmpVersion;
|
||||||
|
if (kvmfrVersion)
|
||||||
|
*kvmfrVersion = header.kvmfrVersion;
|
||||||
|
}
|
||||||
|
LG_UNLOCK(this->recoveryLock);
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
static LGMP_STATUS lgmp_initializeClient(struct LG_Transport * this)
|
||||||
|
{
|
||||||
|
LGMP_STATUS status = lgmpClientInit(this->shm.mem, this->lgmpSize,
|
||||||
|
&this->client);
|
||||||
|
if (status != LGMP_OK)
|
||||||
|
return status;
|
||||||
|
|
||||||
|
if (!lgmpInput_create(this->client, &this->input))
|
||||||
|
{
|
||||||
|
lgmpClientFree(&this->client);
|
||||||
|
return LGMP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
return LGMP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
static bool lgmp_create(LG_Transport ** result)
|
static bool lgmp_create(LG_Transport ** result)
|
||||||
{
|
{
|
||||||
struct LG_Transport * this = calloc(1, sizeof(*this));
|
struct LG_Transport * this = calloc(1, sizeof(*this));
|
||||||
@@ -209,6 +350,7 @@ static bool lgmp_create(LG_Transport ** result)
|
|||||||
|
|
||||||
LG_LOCK_INIT(this->frameLock);
|
LG_LOCK_INIT(this->frameLock);
|
||||||
LG_LOCK_INIT(this->pointerLock);
|
LG_LOCK_INIT(this->pointerLock);
|
||||||
|
LG_LOCK_INIT(this->recoveryLock);
|
||||||
|
|
||||||
this->frameGeneration = 1;
|
this->frameGeneration = 1;
|
||||||
this->frameLease[0].subscription = &this->frameQueue;
|
this->frameLease[0].subscription = &this->frameQueue;
|
||||||
@@ -220,28 +362,43 @@ static bool lgmp_create(LG_Transport ** result)
|
|||||||
{
|
{
|
||||||
LG_LOCK_FREE(this->frameLock);
|
LG_LOCK_FREE(this->frameLock);
|
||||||
LG_LOCK_FREE(this->pointerLock);
|
LG_LOCK_FREE(this->pointerLock);
|
||||||
|
LG_LOCK_FREE(this->recoveryLock);
|
||||||
free(this);
|
free(this);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
LGMP_STATUS status = lgmpClientInit(this->shm.mem, this->shm.size,
|
this->lgmpSize = this->shm.size;
|
||||||
&this->client);
|
if (this->shm.size >= KVMFR_R_REGION_SIZE + sizeof(KVMFRR))
|
||||||
|
{
|
||||||
|
this->recovery = (KVMFRR *)((uint8_t *)this->shm.mem +
|
||||||
|
this->shm.size - KVMFR_R_REGION_SIZE);
|
||||||
|
if (lgmp_recoveryMagic(this->recovery))
|
||||||
|
{
|
||||||
|
LG_LOCK(this->recoveryLock);
|
||||||
|
lgmp_refreshRecoveryLocked(this, true);
|
||||||
|
LG_UNLOCK(this->recoveryLock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LGMP_STATUS status = lgmp_initializeClient(this);
|
||||||
if (status != LGMP_OK)
|
if (status != LGMP_OK)
|
||||||
{
|
{
|
||||||
|
if (this->recoveryLive &&
|
||||||
|
(status == LGMP_ERR_INVALID_MAGIC ||
|
||||||
|
status == LGMP_ERR_INVALID_VERSION))
|
||||||
|
{
|
||||||
|
DEBUG_WARN("LGMP is unavailable (%s), recovery remains available",
|
||||||
|
lgmpStatusString(status));
|
||||||
|
lgmpClientFree(&this->client);
|
||||||
|
*result = this;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
DEBUG_ERROR("lgmpClientInit failed: %s", lgmpStatusString(status));
|
DEBUG_ERROR("lgmpClientInit failed: %s", lgmpStatusString(status));
|
||||||
ivshmemClose(&this->shm);
|
ivshmemClose(&this->shm);
|
||||||
LG_LOCK_FREE(this->frameLock);
|
LG_LOCK_FREE(this->frameLock);
|
||||||
LG_LOCK_FREE(this->pointerLock);
|
LG_LOCK_FREE(this->pointerLock);
|
||||||
free(this);
|
LG_LOCK_FREE(this->recoveryLock);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!lgmpInput_create(this->client, &this->input))
|
|
||||||
{
|
|
||||||
lgmpClientFree(&this->client);
|
|
||||||
ivshmemClose(&this->shm);
|
|
||||||
LG_LOCK_FREE(this->frameLock);
|
|
||||||
LG_LOCK_FREE(this->pointerLock);
|
|
||||||
free(this);
|
free(this);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -371,29 +528,66 @@ static void lgmp_destroy(LG_Transport ** transport)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
struct LG_Transport * this = *transport;
|
struct LG_Transport * this = *transport;
|
||||||
|
if (this->client)
|
||||||
|
{
|
||||||
lgmpInput_destroy(&this->input);
|
lgmpInput_destroy(&this->input);
|
||||||
lgmp_closeQueues(this);
|
lgmp_closeQueues(this);
|
||||||
|
lgmpClientFree(&this->client);
|
||||||
|
}
|
||||||
lgmp_closeDMA(this);
|
lgmp_closeDMA(this);
|
||||||
free(this->pointerData);
|
free(this->pointerData);
|
||||||
lgmpClientFree(&this->client);
|
|
||||||
ivshmemClose(&this->shm);
|
ivshmemClose(&this->shm);
|
||||||
LG_LOCK_FREE(this->frameLock);
|
LG_LOCK_FREE(this->frameLock);
|
||||||
LG_LOCK_FREE(this->pointerLock);
|
LG_LOCK_FREE(this->pointerLock);
|
||||||
|
LG_LOCK_FREE(this->recoveryLock);
|
||||||
free(this);
|
free(this);
|
||||||
*transport = NULL;
|
*transport = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool lgmp_parseSession(const uint8_t * data, uint32_t size,
|
static void lgmp_setVersionMismatch(LG_TransportSession * session,
|
||||||
LG_TransportSession * session)
|
const char * component, uint32_t expected, uint32_t current)
|
||||||
{
|
{
|
||||||
if (size < sizeof(KVMFR))
|
session->versionMismatch.valid = true;
|
||||||
|
session->versionMismatch.expectedVersion = expected;
|
||||||
|
session->versionMismatch.currentVersion = current;
|
||||||
|
str_copy(session->versionMismatch.component,
|
||||||
|
sizeof(session->versionMismatch.component), component,
|
||||||
|
strlen(component));
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool lgmp_parseSession(struct LG_Transport * this,
|
||||||
|
const uint8_t * data, uint32_t size, LG_TransportSession * session)
|
||||||
|
{
|
||||||
|
if (!data || size < sizeof(KVMFR))
|
||||||
|
{
|
||||||
|
uint32_t current = 0;
|
||||||
|
bool currentValid = data &&
|
||||||
|
size >= offsetof(KVMFR, version) + sizeof(uint32_t);
|
||||||
|
if (currentValid)
|
||||||
|
{
|
||||||
|
memcpy(¤t, data + offsetof(KVMFR, version), sizeof(current));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
currentValid = lgmp_recoveryVersions(this, NULL, ¤t);
|
||||||
|
if (currentValid && current != KVMFR_VERSION)
|
||||||
|
lgmp_setVersionMismatch(session, "KVMFR", KVMFR_VERSION, current);
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const KVMFR * header = (const KVMFR *)data;
|
const KVMFR * header = (const KVMFR *)data;
|
||||||
if (memcmp(header->magic, KVMFR_MAGIC, sizeof(header->magic)) != 0 ||
|
if (memcmp(header->magic, KVMFR_MAGIC, sizeof(header->magic)) != 0)
|
||||||
header->version != KVMFR_VERSION)
|
|
||||||
{
|
{
|
||||||
session->remoteVersion = header->version;
|
uint32_t current;
|
||||||
|
if (lgmp_recoveryVersions(this, NULL, ¤t) &&
|
||||||
|
current != KVMFR_VERSION)
|
||||||
|
lgmp_setVersionMismatch(session, "KVMFR", KVMFR_VERSION, current);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header->version != KVMFR_VERSION)
|
||||||
|
{
|
||||||
|
lgmp_setVersionMismatch(session, "KVMFR", KVMFR_VERSION,
|
||||||
|
header->version);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -466,16 +660,43 @@ static LG_TransportStatus lgmp_connect(LG_Transport * this,
|
|||||||
memset(session, 0, sizeof(*session));
|
memset(session, 0, sizeof(*session));
|
||||||
session->os = LG_TRANSPORT_OS_OTHER;
|
session->os = LG_TRANSPORT_OS_OTHER;
|
||||||
|
|
||||||
|
if (!this->client)
|
||||||
|
{
|
||||||
|
LG_LOCK(this->recoveryLock);
|
||||||
|
lgmp_refreshRecoveryLocked(this,
|
||||||
|
lgmp_recoveryMagic(this->recovery));
|
||||||
|
LG_UNLOCK(this->recoveryLock);
|
||||||
|
|
||||||
|
const LGMP_STATUS status = lgmp_initializeClient(this);
|
||||||
|
if (status != LGMP_OK)
|
||||||
|
{
|
||||||
|
uint32_t remoteVersion = 0;
|
||||||
|
const bool versionKnown =
|
||||||
|
lgmp_recoveryVersions(this, &remoteVersion, NULL);
|
||||||
|
if (status == LGMP_ERR_INVALID_VERSION ||
|
||||||
|
(status == LGMP_ERR_INVALID_MAGIC && versionKnown &&
|
||||||
|
remoteVersion != LGMP_PROTOCOL_VERSION))
|
||||||
|
{
|
||||||
|
if (versionKnown && remoteVersion != LGMP_PROTOCOL_VERSION)
|
||||||
|
lgmp_setVersionMismatch(session, "LGMP",
|
||||||
|
LGMP_PROTOCOL_VERSION, remoteVersion);
|
||||||
|
return LG_TRANSPORT_INVALID_VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
return status == LGMP_ERR_INVALID_MAGIC ?
|
||||||
|
LG_TRANSPORT_UNAVAILABLE : LG_TRANSPORT_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t size;
|
uint32_t size;
|
||||||
uint8_t * data;
|
uint8_t * data;
|
||||||
uint32_t remoteVersion;
|
uint32_t remoteVersion = 0;
|
||||||
LGMP_STATUS status = lgmpClientSessionInit(this->client, &size, &data,
|
LGMP_STATUS status = lgmpClientSessionInit(this->client, &size, &data,
|
||||||
&this->clientID, &remoteVersion);
|
&this->clientID, &remoteVersion);
|
||||||
session->remoteVersion = remoteVersion;
|
|
||||||
switch (status)
|
switch (status)
|
||||||
{
|
{
|
||||||
case LGMP_OK:
|
case LGMP_OK:
|
||||||
if (!lgmp_parseSession(data, size, session))
|
if (!lgmp_parseSession(this, data, size, session))
|
||||||
return LG_TRANSPORT_INVALID_VERSION;
|
return LG_TRANSPORT_INVALID_VERSION;
|
||||||
|
|
||||||
LG_LOCK(this->frameLock);
|
LG_LOCK(this->frameLock);
|
||||||
@@ -494,10 +715,23 @@ static LG_TransportStatus lgmp_connect(LG_Transport * this,
|
|||||||
return LG_TRANSPORT_OK;
|
return LG_TRANSPORT_OK;
|
||||||
|
|
||||||
case LGMP_ERR_INVALID_VERSION:
|
case LGMP_ERR_INVALID_VERSION:
|
||||||
|
if (!remoteVersion)
|
||||||
|
lgmp_recoveryVersions(this, &remoteVersion, NULL);
|
||||||
|
lgmp_setVersionMismatch(session, "LGMP", LGMP_PROTOCOL_VERSION,
|
||||||
|
remoteVersion);
|
||||||
return LG_TRANSPORT_INVALID_VERSION;
|
return LG_TRANSPORT_INVALID_VERSION;
|
||||||
|
|
||||||
case LGMP_ERR_INVALID_SESSION:
|
case LGMP_ERR_INVALID_SESSION:
|
||||||
|
return LG_TRANSPORT_UNAVAILABLE;
|
||||||
|
|
||||||
case LGMP_ERR_INVALID_MAGIC:
|
case LGMP_ERR_INVALID_MAGIC:
|
||||||
|
if (lgmp_recoveryVersions(this, &remoteVersion, NULL) &&
|
||||||
|
remoteVersion != LGMP_PROTOCOL_VERSION)
|
||||||
|
{
|
||||||
|
lgmp_setVersionMismatch(session, "LGMP", LGMP_PROTOCOL_VERSION,
|
||||||
|
remoteVersion);
|
||||||
|
return LG_TRANSPORT_INVALID_VERSION;
|
||||||
|
}
|
||||||
return LG_TRANSPORT_UNAVAILABLE;
|
return LG_TRANSPORT_UNAVAILABLE;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -508,6 +742,9 @@ static LG_TransportStatus lgmp_connect(LG_Transport * this,
|
|||||||
|
|
||||||
static void lgmp_disconnect(LG_Transport * this)
|
static void lgmp_disconnect(LG_Transport * this)
|
||||||
{
|
{
|
||||||
|
if (!this->client)
|
||||||
|
return;
|
||||||
|
|
||||||
lgmpInput_disconnect(this->input);
|
lgmpInput_disconnect(this->input);
|
||||||
lgmp_closeQueues(this);
|
lgmp_closeQueues(this);
|
||||||
|
|
||||||
@@ -528,12 +765,295 @@ static void lgmp_disconnect(LG_Transport * this)
|
|||||||
|
|
||||||
static bool lgmp_sessionValid(LG_Transport * this)
|
static bool lgmp_sessionValid(LG_Transport * this)
|
||||||
{
|
{
|
||||||
return this->connected && lgmpClientSessionValid(this->client);
|
return this->client && this->connected &&
|
||||||
|
lgmpClientSessionValid(this->client);
|
||||||
|
}
|
||||||
|
|
||||||
|
static LG_RecoveryRequest lgmp_recoveryRequestType(uint32_t request)
|
||||||
|
{
|
||||||
|
switch (request)
|
||||||
|
{
|
||||||
|
case KVMFR_R_REQ_NORMAL:
|
||||||
|
return LG_RECOVERY_REQ_NORMAL;
|
||||||
|
case KVMFR_R_REQ_RECOVERY:
|
||||||
|
return LG_RECOVERY_REQ_RECOVERY;
|
||||||
|
default:
|
||||||
|
return LG_RECOVERY_REQ_NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static LG_RecoveryState lgmp_recoveryState(uint32_t state)
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case KVMFR_R_STATE_NORMAL:
|
||||||
|
return LG_RECOVERY_STATE_NORMAL;
|
||||||
|
case KVMFR_R_STATE_SWITCHING:
|
||||||
|
return LG_RECOVERY_STATE_SWITCHING;
|
||||||
|
case KVMFR_R_STATE_ACTIVE:
|
||||||
|
return LG_RECOVERY_STATE_ACTIVE;
|
||||||
|
case KVMFR_R_STATE_FAILED:
|
||||||
|
return LG_RECOVERY_STATE_FAILED;
|
||||||
|
default:
|
||||||
|
return LG_RECOVERY_STATE_UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static LG_RecoveryError lgmp_recoveryError(uint32_t error)
|
||||||
|
{
|
||||||
|
switch (error)
|
||||||
|
{
|
||||||
|
case KVMFR_R_ERR_NONE:
|
||||||
|
return LG_RECOVERY_ERR_NONE;
|
||||||
|
case KVMFR_R_ERR_UNSUPPORTED:
|
||||||
|
return LG_RECOVERY_ERR_UNSUPPORTED;
|
||||||
|
case KVMFR_R_ERR_HELPER_UNAVAILABLE:
|
||||||
|
return LG_RECOVERY_ERR_HELPER_UNAVAILABLE;
|
||||||
|
case KVMFR_R_ERR_TOPOLOGY_FAILED:
|
||||||
|
return LG_RECOVERY_ERR_TOPOLOGY_FAILED;
|
||||||
|
case KVMFR_R_ERR_NO_FALLBACK_DISPLAY:
|
||||||
|
return LG_RECOVERY_ERR_NO_FALLBACK_DISPLAY;
|
||||||
|
default:
|
||||||
|
return LG_RECOVERY_ERR_UNSUPPORTED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool lgmp_recoveryRequestSnapshot(const KVMFRRRequest * source,
|
||||||
|
KVMFRRRequest * result)
|
||||||
|
{
|
||||||
|
for (unsigned i = 0; i < 4; ++i)
|
||||||
|
{
|
||||||
|
const uint32_t serial = __atomic_load_n(&source->serial,
|
||||||
|
__ATOMIC_ACQUIRE);
|
||||||
|
if (serial & KVMFR_R_REQ_WRITING)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
result->request = source->request;
|
||||||
|
result->session = source->session;
|
||||||
|
__atomic_thread_fence(__ATOMIC_ACQUIRE);
|
||||||
|
if (__atomic_load_n(&source->serial, __ATOMIC_RELAXED) == serial)
|
||||||
|
{
|
||||||
|
result->serial = serial;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool lgmp_recoveryStatusSnapshot(const KVMFRRStatus * source,
|
||||||
|
KVMFRRStatus * result)
|
||||||
|
{
|
||||||
|
for (unsigned i = 0; i < 4; ++i)
|
||||||
|
{
|
||||||
|
const uint32_t serial = __atomic_load_n(&source->serial,
|
||||||
|
__ATOMIC_ACQUIRE);
|
||||||
|
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;
|
||||||
|
__atomic_thread_fence(__ATOMIC_ACQUIRE);
|
||||||
|
if (__atomic_load_n(&source->serial, __ATOMIC_RELAXED) == serial)
|
||||||
|
{
|
||||||
|
result->serial = serial;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool lgmp_recoverySerialNewer(uint32_t serial, uint32_t reference)
|
||||||
|
{
|
||||||
|
const uint32_t difference = serial - reference;
|
||||||
|
return difference && difference < 0x80000000U;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool lgmp_recoveryLatestRequest(const KVMFRR * recovery,
|
||||||
|
uint64_t session, KVMFRRRequest * result)
|
||||||
|
{
|
||||||
|
bool found = false;
|
||||||
|
for (unsigned i = 0; i < KVMFR_R_REQ_SLOTS; ++i)
|
||||||
|
{
|
||||||
|
KVMFRRRequest request;
|
||||||
|
if (!lgmp_recoveryRequestSnapshot(&recovery->requests[i], &request) ||
|
||||||
|
!request.serial || request.session != session)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!found || lgmp_recoverySerialNewer(request.serial, result->serial))
|
||||||
|
{
|
||||||
|
*result = request;
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
static LG_TransportStatus lgmp_getRecoveryInfo(LG_Transport * this,
|
||||||
|
LG_RecoveryInfo * info)
|
||||||
|
{
|
||||||
|
if (!info)
|
||||||
|
return LG_TRANSPORT_ERROR;
|
||||||
|
|
||||||
|
memset(info, 0, sizeof(*info));
|
||||||
|
LG_LOCK(this->recoveryLock);
|
||||||
|
const bool live = lgmp_refreshRecoveryLocked(this,
|
||||||
|
lgmp_recoveryMagic(this->recovery));
|
||||||
|
if (!live)
|
||||||
|
{
|
||||||
|
LG_UNLOCK(this->recoveryLock);
|
||||||
|
return LG_TRANSPORT_UNAVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
KVMFRRHeader header;
|
||||||
|
KVMFRRInfo wireInfo;
|
||||||
|
if (!lgmp_recoverySnapshot(this, &header, &wireInfo) ||
|
||||||
|
header.session != this->recoverySession)
|
||||||
|
{
|
||||||
|
LG_UNLOCK(this->recoveryLock);
|
||||||
|
return LG_TRANSPORT_UNAVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
info->abiVersion = header.abiVersion;
|
||||||
|
info->instance = header.session;
|
||||||
|
info->heartbeat = header.heartbeat;
|
||||||
|
if (header.capabilities & KVMFR_R_CAP_DISPLAY)
|
||||||
|
info->capabilities |= LG_RECOVERY_CAP_DISPLAY;
|
||||||
|
|
||||||
|
memcpy(info->uuid, header.uuid, sizeof(info->uuid));
|
||||||
|
for (unsigned i = 0; i < sizeof(info->uuid); ++i)
|
||||||
|
info->uuidValid |= info->uuid[i] != 0;
|
||||||
|
str_copy(info->producerVersion, sizeof(info->producerVersion),
|
||||||
|
wireInfo.version, sizeof(wireInfo.version));
|
||||||
|
|
||||||
|
info->versionCount = 2;
|
||||||
|
str_copy(info->versions[0].component,
|
||||||
|
sizeof(info->versions[0].component), "LGMP", sizeof("LGMP"));
|
||||||
|
info->versions[0].version = header.lgmpVersion;
|
||||||
|
str_copy(info->versions[1].component,
|
||||||
|
sizeof(info->versions[1].component), "KVMFR", sizeof("KVMFR"));
|
||||||
|
info->versions[1].version = header.kvmfrVersion;
|
||||||
|
|
||||||
|
KVMFRRRequest request = {0};
|
||||||
|
if (lgmp_recoveryLatestRequest(this->recovery, header.session, &request))
|
||||||
|
{
|
||||||
|
info->requestSerial = request.serial;
|
||||||
|
info->request = lgmp_recoveryRequestType(request.request);
|
||||||
|
}
|
||||||
|
|
||||||
|
KVMFRRStatus status;
|
||||||
|
if (lgmp_recoveryStatusSnapshot(&this->recovery->status, &status) &&
|
||||||
|
status.session == header.session)
|
||||||
|
{
|
||||||
|
info->ackSerial = status.ackSerial;
|
||||||
|
info->ackRequest = lgmp_recoveryRequestType(status.ackRequest);
|
||||||
|
info->state = lgmp_recoveryState(status.state);
|
||||||
|
info->error = lgmp_recoveryError(status.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
LG_UNLOCK(this->recoveryLock);
|
||||||
|
return LG_TRANSPORT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static LG_TransportStatus lgmp_requestRecovery(LG_Transport * this,
|
||||||
|
LG_RecoveryRequest request, uint32_t * serial)
|
||||||
|
{
|
||||||
|
uint32_t wireRequest;
|
||||||
|
switch (request)
|
||||||
|
{
|
||||||
|
case LG_RECOVERY_REQ_NORMAL:
|
||||||
|
wireRequest = KVMFR_R_REQ_NORMAL;
|
||||||
|
break;
|
||||||
|
case LG_RECOVERY_REQ_RECOVERY:
|
||||||
|
wireRequest = KVMFR_R_REQ_RECOVERY;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return LG_TRANSPORT_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
LG_LOCK(this->recoveryLock);
|
||||||
|
if (!lgmp_refreshRecoveryLocked(this,
|
||||||
|
lgmp_recoveryMagic(this->recovery)))
|
||||||
|
{
|
||||||
|
LG_UNLOCK(this->recoveryLock);
|
||||||
|
return LG_TRANSPORT_UNAVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
KVMFRRHeader header;
|
||||||
|
KVMFRRInfo wireInfo;
|
||||||
|
if (!lgmp_recoverySnapshot(this, &header, &wireInfo) ||
|
||||||
|
header.session != this->recoverySession ||
|
||||||
|
!(header.capabilities & KVMFR_R_CAP_DISPLAY))
|
||||||
|
{
|
||||||
|
LG_UNLOCK(this->recoveryLock);
|
||||||
|
return LG_TRANSPORT_UNAVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ticket = __atomic_add_fetch(&this->recovery->req.ticket, 2U,
|
||||||
|
__ATOMIC_RELAXED);
|
||||||
|
if (!ticket)
|
||||||
|
ticket = __atomic_add_fetch(&this->recovery->req.ticket, 2U,
|
||||||
|
__ATOMIC_RELAXED);
|
||||||
|
if (!ticket || (ticket & KVMFR_R_REQ_WRITING))
|
||||||
|
{
|
||||||
|
LG_UNLOCK(this->recoveryLock);
|
||||||
|
return LG_TRANSPORT_UNAVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
KVMFRRRequest * destination = NULL;
|
||||||
|
const uint32_t claimed = ticket | KVMFR_R_REQ_WRITING;
|
||||||
|
for (unsigned i = 0; i < KVMFR_R_REQ_SLOTS; ++i)
|
||||||
|
{
|
||||||
|
uint32_t expected = 0;
|
||||||
|
if (__atomic_compare_exchange_n(&this->recovery->requests[i].serial,
|
||||||
|
&expected, claimed, false, __ATOMIC_ACQ_REL, __ATOMIC_RELAXED))
|
||||||
|
{
|
||||||
|
destination = &this->recovery->requests[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!destination)
|
||||||
|
{
|
||||||
|
LG_UNLOCK(this->recoveryLock);
|
||||||
|
return LG_TRANSPORT_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
destination->request = wireRequest;
|
||||||
|
destination->session = header.session;
|
||||||
|
|
||||||
|
KVMFRRHeader currentHeader;
|
||||||
|
if (!lgmp_recoverySnapshot(this, ¤tHeader, &wireInfo) ||
|
||||||
|
currentHeader.session != header.session)
|
||||||
|
{
|
||||||
|
uint32_t expected = claimed;
|
||||||
|
__atomic_compare_exchange_n(&destination->serial, &expected, 0, false,
|
||||||
|
__ATOMIC_RELEASE, __ATOMIC_RELAXED);
|
||||||
|
LG_UNLOCK(this->recoveryLock);
|
||||||
|
return LG_TRANSPORT_UNAVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t expected = claimed;
|
||||||
|
if (!__atomic_compare_exchange_n(&destination->serial, &expected, ticket,
|
||||||
|
false, __ATOMIC_RELEASE, __ATOMIC_RELAXED))
|
||||||
|
{
|
||||||
|
LG_UNLOCK(this->recoveryLock);
|
||||||
|
return LG_TRANSPORT_TIMEOUT;
|
||||||
|
}
|
||||||
|
if (serial)
|
||||||
|
*serial = ticket;
|
||||||
|
|
||||||
|
LG_UNLOCK(this->recoveryLock);
|
||||||
|
return LG_TRANSPORT_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool lgmp_supportsDMA(LG_Transport * this)
|
static bool lgmp_supportsDMA(LG_Transport * this)
|
||||||
{
|
{
|
||||||
return this->allowDMA && ivshmemHasDMA(&this->shm);
|
return this->client && this->allowDMA && ivshmemHasDMA(&this->shm);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool lgmp_attachRenderer(LG_Transport * this,
|
static bool lgmp_attachRenderer(LG_Transport * this,
|
||||||
@@ -801,10 +1321,23 @@ static int lgmp_getDMA(struct LG_Transport * this, const KVMFRFrame * frame,
|
|||||||
if (dma->fd >= 0)
|
if (dma->fd >= 0)
|
||||||
return dma->fd;
|
return dma->fd;
|
||||||
|
|
||||||
const uintptr_t position = (uintptr_t)frame - (uintptr_t)this->shm.mem;
|
const uintptr_t base = (uintptr_t)this->shm.mem;
|
||||||
const uintptr_t offset = frame->offset + sizeof(FrameBuffer);
|
const uintptr_t address = (uintptr_t)frame;
|
||||||
|
if (address < base)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
const size_t position = address - base;
|
||||||
|
if (position > this->lgmpSize ||
|
||||||
|
frame->offset > this->lgmpSize - position ||
|
||||||
|
sizeof(FrameBuffer) > this->lgmpSize - position - frame->offset)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
const size_t offset = position + frame->offset + sizeof(FrameBuffer);
|
||||||
|
if (dataSize > this->lgmpSize - offset)
|
||||||
|
return -1;
|
||||||
|
|
||||||
dma->dataSize = dataSize;
|
dma->dataSize = dataSize;
|
||||||
dma->fd = ivshmemGetDMABuf(&this->shm, position + offset, dataSize);
|
dma->fd = ivshmemGetDMABuf(&this->shm, offset, dataSize);
|
||||||
return dma->fd;
|
return dma->fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1397,6 +1930,8 @@ const LG_TransportOps LGT_LGMP =
|
|||||||
.sessionValid = lgmp_sessionValid,
|
.sessionValid = lgmp_sessionValid,
|
||||||
.getVideoOps = lgmp_getVideoOps,
|
.getVideoOps = lgmp_getVideoOps,
|
||||||
.getInputOps = lgmp_getInputOps,
|
.getInputOps = lgmp_getInputOps,
|
||||||
|
.getRecoveryInfo = lgmp_getRecoveryInfo,
|
||||||
|
.requestRecovery = lgmp_requestRecovery,
|
||||||
.sendControl = lgmp_sendControl,
|
.sendControl = lgmp_sendControl,
|
||||||
.controlStatus = lgmp_controlStatus,
|
.controlStatus = lgmp_controlStatus,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user