[input] lgmp: make report streams mandatory

Carry every input activation and report through its client-bound SPSC
stream. Keep the LGMP queue only for status publication and subscriber
discovery, removing transport selection and the queue-report fallback.

Retain graceful endpoint draining when a subscriber disappears and use
bounded adaptive polling while streams are active. The IDD polling setup
remains compatible with the project’s current C++ language mode.
This commit is contained in:
Geoffrey McRae
2026-08-15 14:20:59 +10:00
parent 89b1ced8fc
commit 12801df353
4 changed files with 225 additions and 502 deletions

View File

@@ -59,17 +59,9 @@ enum LGMPInputMouseMode
LGMP_INPUT_MOUSE_ABSOLUTE, LGMP_INPUT_MOUSE_ABSOLUTE,
}; };
enum LGMPInputMessageTransport
{
LGMP_INPUT_TRANSPORT_NONE,
LGMP_INPUT_TRANSPORT_QUEUE,
LGMP_INPUT_TRANSPORT_STREAM,
};
struct LGMPInputPending struct LGMPInputPending
{ {
KVMFRInputMessage message; KVMFRInputMessage message;
enum LGMPInputMessageTransport transport;
bool pureMotion; bool pureMotion;
}; };
@@ -78,9 +70,7 @@ struct LGMPInputCounters
uint64_t immediateSends; uint64_t immediateSends;
uint64_t deferredSends; uint64_t deferredSends;
uint64_t localEnqueues; uint64_t localEnqueues;
uint64_t initialBusy;
uint64_t initialFull; uint64_t initialFull;
uint64_t retryBusy;
uint64_t retryFull; uint64_t retryFull;
uint64_t relativeCoalesces; uint64_t relativeCoalesces;
uint64_t absoluteCoalesces; uint64_t absoluteCoalesces;
@@ -92,9 +82,6 @@ struct LGMPInputCounters
uint64_t releases; uint64_t releases;
uint64_t keepalives; uint64_t keepalives;
uint64_t terminalFailures; uint64_t terminalFailures;
uint64_t ackTotal;
uint64_t ackMax;
uint64_t ackSamples;
unsigned pendingHighWater; unsigned pendingHighWater;
}; };
@@ -102,11 +89,6 @@ struct LGMPInputStats
{ {
struct LGMPInputCounters counters; struct LGMPInputCounters counters;
uint64_t lastReport; uint64_t lastReport;
uint64_t nextProbe;
uint64_t probeStart;
uint32_t probeSerial;
bool probeOutstanding;
bool probeDue;
}; };
struct LGMPInput struct LGMPInput
@@ -127,9 +109,6 @@ struct LGMPInput
uint32_t sequence; uint32_t sequence;
uint32_t publishedGeneration; uint32_t publishedGeneration;
uint32_t publishedSequence; uint32_t publishedSequence;
enum LGMPInputMessageTransport publishedTransport;
enum LGMPInputMessageTransport activeTransport;
bool queueOnly;
bool publishedClaimed; bool publishedClaimed;
uint64_t lastSend; uint64_t lastSend;
@@ -146,9 +125,7 @@ struct LGMPInput
uint32_t clientID; uint32_t clientID;
uint32_t capabilities; uint32_t capabilities;
KVMFRInputTransportFlags transports;
KVMFRInputStreamEndpoint streamEndpoint; KVMFRInputStreamEndpoint streamEndpoint;
uint32_t streamGeneration;
uint32_t endpointGeneration; uint32_t endpointGeneration;
uint32_t statusSerial; uint32_t statusSerial;
uint32_t statusOwnerClientID; uint32_t statusOwnerClientID;
@@ -211,12 +188,10 @@ static void notifyInputStatus(LGMPInput * input)
} }
static void published(LGMPInput * input, static void published(LGMPInput * input,
const KVMFRInputMessage * message, const KVMFRInputMessage * message)
enum LGMPInputMessageTransport transport)
{ {
input->publishedGeneration = message->generation; input->publishedGeneration = message->generation;
input->publishedSequence = message->sequence; input->publishedSequence = message->sequence;
input->publishedTransport = transport;
input->lastSend = microtime(); input->lastSend = microtime();
switch (message->type) switch (message->type)
@@ -228,9 +203,6 @@ static void published(LGMPInput * input,
case KVMFR_INPUT_MESSAGE_RELEASE: case KVMFR_INPUT_MESSAGE_RELEASE:
input->publishedClaimed = false; input->publishedClaimed = false;
input->publishedTransport = LGMP_INPUT_TRANSPORT_NONE;
if (message->generation == input->generation && !input->claimed)
input->activeTransport = LGMP_INPUT_TRANSPORT_NONE;
++input->stats.counters.releases; ++input->stats.counters.releases;
break; break;
@@ -266,38 +238,27 @@ static void connectionFailed(LGMPInput * input, LGMP_STATUS status)
input->mouseMode = LGMP_INPUT_MOUSE_NONE; input->mouseMode = LGMP_INPUT_MOUSE_NONE;
input->mouseButtons = 0; input->mouseButtons = 0;
input->publishedClaimed = false; input->publishedClaimed = false;
input->publishedTransport = LGMP_INPUT_TRANSPORT_NONE;
input->activeTransport = LGMP_INPUT_TRANSPORT_NONE;
input->queueOnly = false;
input->lastInput = 0; input->lastInput = 0;
input->capabilities = 0; input->capabilities = 0;
input->transports = 0;
input->streamGeneration = 0;
input->streamEndpointBound = false; input->streamEndpointBound = false;
detachInputStream(input); detachInputStream(input);
memset(&input->streamEndpoint, 0, memset(&input->streamEndpoint, 0,
sizeof(input->streamEndpoint)); sizeof(input->streamEndpoint));
input->statusValid = false; input->statusValid = false;
input->ownerConfirmed = false; input->ownerConfirmed = false;
input->stats.probeOutstanding = false;
memset(input->keyState, 0, sizeof(input->keyState)); memset(input->keyState, 0, sizeof(input->keyState));
atomic_store_explicit(&input->stop, true, memory_order_release); atomic_store_explicit(&input->stop, true, memory_order_release);
} }
static LGMP_STATUS trySend(LGMPInput * input, static LGMP_STATUS trySend(LGMPInput * input,
const KVMFRInputMessage * message, const KVMFRInputMessage * message, bool deferred)
enum LGMPInputMessageTransport transport, bool deferred)
{ {
bool probe = false;
uint32_t serial = 0;
LGMP_STATUS status;
if (transport == LGMP_INPUT_TRANSPORT_STREAM)
{
if (!input->stream) if (!input->stream)
return LGMP_ERR_STREAM_UNBOUND; return LGMP_ERR_STREAM_UNBOUND;
LGMPStreamBuffer buffer = { 0 }; LGMPStreamBuffer buffer = { 0 };
status = lgmpClientStreamWriteAcquire(input->stream, &buffer); LGMP_STATUS status = lgmpClientStreamWriteAcquire(
input->stream, &buffer);
if (status == LGMP_OK) if (status == LGMP_OK)
{ {
if (buffer.capacity < sizeof(*message)) if (buffer.capacity < sizeof(*message))
@@ -314,16 +275,6 @@ static LGMP_STATUS trySend(LGMPInput * input,
lgmpClientStreamWriteCancel(input->stream, &buffer); lgmpClientStreamWriteCancel(input->stream, &buffer);
} }
} }
}
else if (transport == LGMP_INPUT_TRANSPORT_QUEUE)
{
probe = input->stats.probeDue &&
!input->stats.probeOutstanding;
status = lgmpClientTrySendData(input->queue,
message, sizeof(*message), probe ? &serial : NULL);
}
else
return LGMP_ERR_INVALID_ARGUMENT;
if (status == LGMP_OK) if (status == LGMP_OK)
{ {
@@ -331,34 +282,10 @@ static LGMP_STATUS trySend(LGMPInput * input,
++input->stats.counters.deferredSends; ++input->stats.counters.deferredSends;
else else
++input->stats.counters.immediateSends; ++input->stats.counters.immediateSends;
if (probe)
{
const uint64_t now = microtime();
input->stats.probeOutstanding = true;
input->stats.probeDue = false;
input->stats.probeSerial = serial;
input->stats.probeStart = now;
input->stats.nextProbe = now + INPUT_STATS_INTERVAL_US;
}
return status; return status;
} }
if (status == LGMP_ERR_QUEUE_BUSY) if (status == LGMP_ERR_STREAM_FULL)
{
if (deferred)
++input->stats.counters.retryBusy;
else
++input->stats.counters.initialBusy;
}
else if (status == LGMP_ERR_QUEUE_FULL)
{
if (deferred)
++input->stats.counters.retryFull;
else
++input->stats.counters.initialFull;
}
else if (status == LGMP_ERR_STREAM_FULL)
{ {
if (deferred) if (deferred)
++input->stats.counters.retryFull; ++input->stats.counters.retryFull;
@@ -370,23 +297,22 @@ static LGMP_STATUS trySend(LGMPInput * input,
static bool retryableSendStatus(LGMP_STATUS status) static bool retryableSendStatus(LGMP_STATUS status)
{ {
return status == LGMP_ERR_QUEUE_BUSY || return status == LGMP_ERR_STREAM_FULL;
status == LGMP_ERR_QUEUE_FULL ||
status == LGMP_ERR_STREAM_FULL;
} }
static void sendFailed(LGMPInput * input, static void sendFailed(LGMPInput * input, LGMP_STATUS status)
enum LGMPInputMessageTransport transport, LGMP_STATUS status)
{ {
if (transport == LGMP_INPUT_TRANSPORT_STREAM && if (status == LGMP_ERR_STREAM_UNBOUND ||
(status == LGMP_ERR_STREAM_UNBOUND || status == LGMP_ERR_STREAM_STALE)
status == LGMP_ERR_STREAM_STALE))
{ {
DEBUG_WARN("LGMP input stream binding was lost: %s", DEBUG_WARN("LGMP input stream binding was lost: %s",
lgmpStatusString(status)); lgmpStatusString(status));
const bool notify = input->available || input->endpointGeneration;
detachInputStream(input); detachInputStream(input);
input->queueOnly = true;
discardProtocolState(input); discardProtocolState(input);
input->available = false;
if (notify)
input->notifyStatus = true;
return; return;
} }
@@ -402,8 +328,7 @@ static bool coalesceMotion(LGMPInput * input,
struct LGMPInputPending * tail = pendingAt( struct LGMPInputPending * tail = pendingAt(
input, input->pendingCount - 1); input, input->pendingCount - 1);
if (!tail->pureMotion || tail->message.type != type || if (!tail->pureMotion || tail->message.type != type ||
tail->message.generation != input->generation || tail->message.generation != input->generation)
tail->transport != input->activeTransport)
return false; return false;
if (type == KVMFR_INPUT_MESSAGE_MOUSE_ABSOLUTE) if (type == KVMFR_INPUT_MESSAGE_MOUSE_ABSOLUTE)
@@ -465,19 +390,16 @@ static bool discardPendingMotion(LGMPInput * input)
return false; return false;
} }
static bool queuePayload(LGMPInput * input, KVMFRInputMessageType type, static bool submitPayload(LGMPInput * input, KVMFRInputMessageType type,
const KVMFRInputPayload * payload, bool pureMotion, bool * wake) const KVMFRInputPayload * payload, bool pureMotion, bool * wake)
{ {
if (!input->connected || !input->queue || if (!input->connected || !input->stream)
input->activeTransport == LGMP_INPUT_TRANSPORT_NONE)
return false; return false;
const bool inputMessage = const bool inputMessage =
type == KVMFR_INPUT_MESSAGE_MOUSE_RELATIVE || type == KVMFR_INPUT_MESSAGE_MOUSE_RELATIVE ||
type == KVMFR_INPUT_MESSAGE_MOUSE_ABSOLUTE || type == KVMFR_INPUT_MESSAGE_MOUSE_ABSOLUTE ||
type == KVMFR_INPUT_MESSAGE_KEYBOARD; type == KVMFR_INPUT_MESSAGE_KEYBOARD;
const enum LGMPInputMessageTransport transport =
input->activeTransport;
if (pureMotion && coalesceMotion(input, type, payload)) if (pureMotion && coalesceMotion(input, type, payload))
{ {
@@ -511,23 +433,19 @@ static bool queuePayload(LGMPInput * input, KVMFRInputMessageType type,
if (!input->pendingCount) if (!input->pendingCount)
{ {
const bool probeOutstanding = input->stats.probeOutstanding; const LGMP_STATUS status = trySend(input, &message, false);
const LGMP_STATUS status = trySend(
input, &message, transport, false);
if (status == LGMP_OK) if (status == LGMP_OK)
{ {
published(input, &message, transport); published(input, &message);
if (inputMessage) if (inputMessage)
input->lastInput = microtime(); input->lastInput = microtime();
if (!probeOutstanding && input->stats.probeOutstanding)
*wake = true;
return true; return true;
} }
if (!retryableSendStatus(status)) if (!retryableSendStatus(status))
{ {
input->sequence = previousSequence; input->sequence = previousSequence;
sendFailed(input, transport, status); sendFailed(input, status);
return false; return false;
} }
} }
@@ -540,7 +458,6 @@ static bool queuePayload(LGMPInput * input, KVMFRInputMessageType type,
struct LGMPInputPending * item = pendingAt(input, input->pendingCount++); struct LGMPInputPending * item = pendingAt(input, input->pendingCount++);
item->message = message; item->message = message;
item->transport = transport;
item->pureMotion = pureMotion; item->pureMotion = pureMotion;
++input->stats.counters.localEnqueues; ++input->stats.counters.localEnqueues;
if (input->pendingCount > input->stats.counters.pendingHighWater) if (input->pendingCount > input->stats.counters.pendingHighWater)
@@ -555,7 +472,8 @@ static bool claim(LGMPInput * input, bool * wake)
{ {
if (input->claimed) if (input->claimed)
return true; return true;
if (!input->available || input->ownerBlocked) if (!input->available || input->ownerBlocked ||
!input->stream || !input->streamEndpointBound)
return false; return false;
if (++input->generation == 0) if (++input->generation == 0)
@@ -563,21 +481,13 @@ static bool claim(LGMPInput * input, bool * wake)
input->sequence = 0; input->sequence = 0;
input->claimed = true; input->claimed = true;
input->ownerConfirmed = false; input->ownerConfirmed = false;
if (!input->queueOnly && input->stream && input->streamEndpointBound)
input->activeTransport = LGMP_INPUT_TRANSPORT_STREAM;
else
{
input->activeTransport = LGMP_INPUT_TRANSPORT_QUEUE;
input->queueOnly = true;
}
const KVMFRInputPayload payload = { 0 }; const KVMFRInputPayload payload = { 0 };
if (queuePayload(input, KVMFR_INPUT_MESSAGE_CLAIM, if (submitPayload(input, KVMFR_INPUT_MESSAGE_CLAIM,
&payload, false, wake)) &payload, false, wake))
return true; return true;
input->claimed = false; input->claimed = false;
input->activeTransport = LGMP_INPUT_TRANSPORT_NONE;
return false; return false;
} }
@@ -611,8 +521,6 @@ static void discardProtocolState(LGMPInput * input)
input->publishedGeneration = 0; input->publishedGeneration = 0;
input->publishedSequence = 0; input->publishedSequence = 0;
input->publishedClaimed = false; input->publishedClaimed = false;
input->publishedTransport = LGMP_INPUT_TRANSPORT_NONE;
input->activeTransport = LGMP_INPUT_TRANSPORT_NONE;
input->ownerConfirmed = false; input->ownerConfirmed = false;
} }
@@ -625,7 +533,7 @@ static bool restoreInputState(LGMPInput * input, bool * wake)
KVMFRInputPayload keyboard = { 0 }; KVMFRInputPayload keyboard = { 0 };
const bool keyboardHeld = buildKeyboardPayload(input, &keyboard); const bool keyboardHeld = buildKeyboardPayload(input, &keyboard);
if (keyboardHeld && !queuePayload(input, KVMFR_INPUT_MESSAGE_KEYBOARD, if (keyboardHeld && !submitPayload(input, KVMFR_INPUT_MESSAGE_KEYBOARD,
&keyboard, false, wake)) &keyboard, false, wake))
return false; return false;
@@ -637,15 +545,6 @@ static bool restoreInputState(LGMPInput * input, bool * wake)
return true; return true;
} }
static bool inputBytesZero(const void * data, size_t size)
{
const uint8_t * bytes = data;
for (size_t i = 0; i < size; ++i)
if (bytes[i])
return false;
return true;
}
static bool validInputStatus(const KVMFRInputStatus * status) static bool validInputStatus(const KVMFRInputStatus * status)
{ {
static const uint32_t capabilities = static const uint32_t capabilities =
@@ -655,18 +554,17 @@ static bool validInputStatus(const KVMFRInputStatus * status)
static const uint32_t flags = static const uint32_t flags =
KVMFR_INPUT_STATUS_AVAILABLE | KVMFR_INPUT_STATUS_AVAILABLE |
KVMFR_INPUT_STATUS_HAS_OWNER; KVMFR_INPUT_STATUS_HAS_OWNER;
static const uint32_t transports =
KVMFR_INPUT_TRANSPORT_QUEUE |
KVMFR_INPUT_TRANSPORT_STREAM;
if (status->version != KVMFR_INPUT_VERSION || if (status->version != KVMFR_INPUT_VERSION ||
status->capabilities & ~capabilities || status->capabilities & ~capabilities ||
status->flags & ~flags || status->flags & ~flags ||
status->transports & ~transports ||
!(status->transports & KVMFR_INPUT_TRANSPORT_QUEUE) ||
!status->generation || !status->generation ||
!status->lease || !status->maxButtons || !status->lease || !status->maxButtons ||
status->maxButtons > KVMFR_INPUT_MOUSE_BUTTON_COUNT) status->maxButtons > KVMFR_INPUT_MOUSE_BUTTON_COUNT ||
status->streamVersion != KVMFR_INPUT_STREAM_VERSION ||
status->streamEndpointCount !=
KVMFR_INPUT_STREAM_ENDPOINT_COUNT ||
!status->streamGeneration)
return false; return false;
for (size_t i = 0; i < sizeof(status->streamReserved) / for (size_t i = 0; i < sizeof(status->streamReserved) /
@@ -674,28 +572,6 @@ static bool validInputStatus(const KVMFRInputStatus * status)
if (status->streamReserved[i]) if (status->streamReserved[i])
return false; return false;
const bool stream =
(status->transports & KVMFR_INPUT_TRANSPORT_STREAM) != 0;
if (!stream)
{
if (status->streamVersion || status->streamEndpointCount ||
status->streamGeneration)
return false;
for (unsigned i = 0;
i < KVMFR_INPUT_STREAM_ENDPOINT_COUNT; ++i)
if (!inputBytesZero(&status->streamEndpoint[i],
sizeof(status->streamEndpoint[i])))
return false;
}
else
{
if (status->streamVersion != KVMFR_INPUT_STREAM_VERSION ||
status->streamEndpointCount !=
KVMFR_INPUT_STREAM_ENDPOINT_COUNT ||
!status->streamGeneration)
return false;
bool endpointAvailable = false;
for (unsigned i = 0; for (unsigned i = 0;
i < KVMFR_INPUT_STREAM_ENDPOINT_COUNT; ++i) i < KVMFR_INPUT_STREAM_ENDPOINT_COUNT; ++i)
{ {
@@ -704,22 +580,13 @@ static bool validInputStatus(const KVMFRInputStatus * status)
const uint32_t endpointFlags = const uint32_t endpointFlags =
KVMFR_INPUT_STREAM_ENDPOINT_AVAILABLE | KVMFR_INPUT_STREAM_ENDPOINT_AVAILABLE |
KVMFR_INPUT_STREAM_ENDPOINT_BOUND; KVMFR_INPUT_STREAM_ENDPOINT_BOUND;
if (endpoint->flags & ~endpointFlags || endpoint->reserved) if (endpoint->flags & ~endpointFlags || endpoint->reserved ||
!(endpoint->flags &
KVMFR_INPUT_STREAM_ENDPOINT_AVAILABLE))
return false; return false;
const bool endpointValid =
(endpoint->flags &
KVMFR_INPUT_STREAM_ENDPOINT_AVAILABLE) != 0;
const bool endpointBound = const bool endpointBound =
(endpoint->flags & KVMFR_INPUT_STREAM_ENDPOINT_BOUND) != 0; (endpoint->flags & KVMFR_INPUT_STREAM_ENDPOINT_BOUND) != 0;
if (!endpointValid)
{
if (!inputBytesZero(endpoint, sizeof(*endpoint)))
return false;
continue;
}
endpointAvailable = true;
if (endpoint->stream.magic != LGMP_STREAM_DESCRIPTOR_MAGIC || if (endpoint->stream.magic != LGMP_STREAM_DESCRIPTOR_MAGIC ||
endpoint->stream.size != sizeof(endpoint->stream) || endpoint->stream.size != sizeof(endpoint->stream) ||
endpoint->stream.version != LGMP_STREAM_DESCRIPTOR_VERSION || endpoint->stream.version != LGMP_STREAM_DESCRIPTOR_VERSION ||
@@ -739,18 +606,12 @@ static bool validInputStatus(const KVMFRInputStatus * status)
{ {
const KVMFRInputStreamEndpoint * previous = const KVMFRInputStreamEndpoint * previous =
&status->streamEndpoint[j]; &status->streamEndpoint[j];
if (!(previous->flags &
KVMFR_INPUT_STREAM_ENDPOINT_AVAILABLE))
continue;
if (previous->stream.offset == endpoint->stream.offset || if (previous->stream.offset == endpoint->stream.offset ||
(endpointBound && (endpointBound &&
previous->boundClientID == endpoint->boundClientID)) previous->boundClientID == endpoint->boundClientID))
return false; return false;
} }
} }
if (!endpointAvailable)
return false;
}
const bool available = const bool available =
(status->flags & KVMFR_INPUT_STATUS_AVAILABLE) != 0; (status->flags & KVMFR_INPUT_STATUS_AVAILABLE) != 0;
@@ -789,7 +650,6 @@ static bool reconcileInputStream(LGMPInput * input,
const KVMFRInputStatus * status) const KVMFRInputStatus * status)
{ {
const KVMFRInputStreamEndpoint * desired = NULL; const KVMFRInputStreamEndpoint * desired = NULL;
if (status->transports & KVMFR_INPUT_TRANSPORT_STREAM)
for (unsigned i = 0; i < status->streamEndpointCount; ++i) for (unsigned i = 0; i < status->streamEndpointCount; ++i)
if ((status->streamEndpoint[i].flags & if ((status->streamEndpoint[i].flags &
KVMFR_INPUT_STREAM_ENDPOINT_BOUND) && KVMFR_INPUT_STREAM_ENDPOINT_BOUND) &&
@@ -858,7 +718,7 @@ static void applyInputStatus(LGMPInput * input,
const bool wasAvailable = input->available; const bool wasAvailable = input->available;
const uint32_t oldCapabilities = input->capabilities; const uint32_t oldCapabilities = input->capabilities;
const uint32_t oldGeneration = input->endpointGeneration; const uint32_t oldGeneration = input->endpointGeneration;
const bool available = const bool targetAvailable =
(status->flags & KVMFR_INPUT_STATUS_AVAILABLE) != 0; (status->flags & KVMFR_INPUT_STATUS_AVAILABLE) != 0;
const bool endpointChanged = wasValid && const bool endpointChanged = wasValid &&
oldGeneration != status->generation; oldGeneration != status->generation;
@@ -866,18 +726,16 @@ static void applyInputStatus(LGMPInput * input,
input->statusValid = true; input->statusValid = true;
input->statusSerial = serial; input->statusSerial = serial;
input->available = available;
input->capabilities = status->capabilities; input->capabilities = status->capabilities;
input->endpointGeneration = status->generation; input->endpointGeneration = status->generation;
input->statusOwnerClientID = status->ownerClientID; input->statusOwnerClientID = status->ownerClientID;
input->statusOwnerGeneration = status->ownerGeneration; input->statusOwnerGeneration = status->ownerGeneration;
input->transports = status->transports;
input->streamGeneration = status->streamGeneration;
const bool streamChanged = reconcileInputStream(input, status); const bool streamChanged = reconcileInputStream(input, status);
const bool available = targetAvailable &&
input->streamEndpointBound;
input->available = available;
if (endpointChanged || !available || if (endpointChanged || !available || streamChanged)
(streamChanged &&
input->activeTransport == LGMP_INPUT_TRANSPORT_STREAM))
{ {
discardProtocolState(input); discardProtocolState(input);
restore = available && (endpointChanged || streamChanged); restore = available && (endpointChanged || streamChanged);
@@ -974,64 +832,37 @@ static bool release(LGMPInput * input, bool * wake)
clearInputState(input); clearInputState(input);
if (!input->publishedClaimed) if (!input->publishedClaimed)
{
input->activeTransport = LGMP_INPUT_TRANSPORT_NONE;
return true; return true;
}
input->generation = input->publishedGeneration; input->generation = input->publishedGeneration;
input->sequence = input->publishedSequence; input->sequence = input->publishedSequence;
input->activeTransport = input->publishedTransport;
const KVMFRInputPayload payload = { 0 }; const KVMFRInputPayload payload = { 0 };
return queuePayload(input, KVMFR_INPUT_MESSAGE_RELEASE, return submitPayload(input, KVMFR_INPUT_MESSAGE_RELEASE,
&payload, false, wake); &payload, false, wake);
} }
static void flushPending(LGMPInput * input) static bool flushPending(LGMPInput * input)
{ {
bool progress = false;
while (input->connected && input->pendingCount) while (input->connected && input->pendingCount)
{ {
struct LGMPInputPending * item = pendingAt(input, 0); struct LGMPInputPending * item = pendingAt(input, 0);
const LGMP_STATUS status = trySend(input, &item->message, const LGMP_STATUS status = trySend(input, &item->message, true);
item->transport, true);
if (retryableSendStatus(status)) if (retryableSendStatus(status))
return; return progress;
if (status != LGMP_OK) if (status != LGMP_OK)
{ {
sendFailed(input, item->transport, status); sendFailed(input, status);
return; return progress;
} }
published(input, &item->message, item->transport); progress = true;
published(input, &item->message);
input->pendingHead = input->pendingHead =
(input->pendingHead + 1) % INPUT_PENDING_LENGTH; (input->pendingHead + 1) % INPUT_PENDING_LENGTH;
--input->pendingCount; --input->pendingCount;
} }
} return progress;
static void pollAckProbe(LGMPInput * input)
{
if (!input->stats.probeOutstanding)
return;
uint32_t processed;
const LGMP_STATUS status =
lgmpClientGetSerial(input->queue, &processed);
if (status != LGMP_OK)
{
input->stats.probeOutstanding = false;
connectionFailed(input, status);
return;
}
if ((int32_t)(processed - input->stats.probeSerial) < 0)
return;
const uint64_t elapsed = microtime() - input->stats.probeStart;
input->stats.probeOutstanding = false;
input->stats.counters.ackTotal += elapsed;
++input->stats.counters.ackSamples;
if (elapsed > input->stats.counters.ackMax)
input->stats.counters.ackMax = elapsed;
} }
static bool collectStats(LGMPInput * input, uint64_t now, bool force, static bool collectStats(LGMPInput * input, uint64_t now, bool force,
@@ -1046,44 +877,36 @@ static bool collectStats(LGMPInput * input, uint64_t now, bool force,
input->stats.counters.pendingHighWater = input->pendingCount; input->stats.counters.pendingHighWater = input->pendingCount;
return result->immediateSends || result->deferredSends || return result->immediateSends || result->deferredSends ||
result->localEnqueues || result->initialBusy || result->localEnqueues || result->initialFull || result->retryFull ||
result->initialFull || result->retryBusy || result->retryFull ||
result->relativeCoalesces || result->absoluteCoalesces || result->relativeCoalesces || result->absoluteCoalesces ||
result->reservedRejects || result->motionEvictions || result->reservedRejects || result->motionEvictions ||
result->discreteOverflowFailures || result->discreteOverflowFailures ||
result->discreteOverflowResets || result->claims || result->discreteOverflowResets || result->claims ||
result->releases || result->keepalives || result->releases || result->keepalives ||
result->terminalFailures || result->ackSamples; result->terminalFailures;
} }
static void logStats(const struct LGMPInputCounters * stats) static void logStats(const struct LGMPInputCounters * stats)
{ {
const uint64_t ackAverage = stats->ackSamples ?
stats->ackTotal / stats->ackSamples : 0;
DEBUG_TRACE("LGMP input: sent immediate/deferred %lu/%lu" DEBUG_TRACE("LGMP input: sent immediate/deferred %lu/%lu"
", queued %lu (high %u), initial busy/full %lu" ", queued %lu (high %u), initial/retry full %lu/%lu"
"/%lu, retry busy/full %lu/%lu"
", coalesced relative/absolute %lu/%lu" ", coalesced relative/absolute %lu/%lu"
", motion rejected/evicted %lu/%lu" ", motion rejected/evicted %lu/%lu"
", overflow failures/resets %lu/%lu" ", overflow failures/resets %lu/%lu"
", published claim/release/keepalive %lu/%lu" ", published claim/release/keepalive %lu/%lu"
"/%lu, terminal failures %lu" "/%lu, terminal failures %lu",
", LGMP ACK average/max %lu/%lu us (%lu" stats->immediateSends, stats->deferredSends,
" samples)", stats->immediateSends, stats->deferredSends,
stats->localEnqueues, stats->pendingHighWater, stats->localEnqueues, stats->pendingHighWater,
stats->initialBusy, stats->initialFull, stats->retryBusy, stats->initialFull, stats->retryFull, stats->relativeCoalesces,
stats->retryFull, stats->relativeCoalesces,
stats->absoluteCoalesces, stats->reservedRejects, stats->absoluteCoalesces, stats->reservedRejects,
stats->motionEvictions, stats->discreteOverflowFailures, stats->motionEvictions, stats->discreteOverflowFailures,
stats->discreteOverflowResets, stats->claims, stats->releases, stats->discreteOverflowResets, stats->claims, stats->releases,
stats->keepalives, stats->terminalFailures, ackAverage, stats->keepalives, stats->terminalFailures);
stats->ackMax, stats->ackSamples);
} }
static void releaseOnDisconnect(LGMPInput * input) static void releaseOnDisconnect(LGMPInput * input)
{ {
if (!input->queue || !input->publishedClaimed || if (!input->queue || !input->stream || !input->publishedClaimed ||
!input->publishedGeneration) !input->publishedGeneration)
return; return;
@@ -1097,14 +920,11 @@ static void releaseOnDisconnect(LGMPInput * input)
message.sequence = 1; message.sequence = 1;
const uint64_t deadline = microtime() + INPUT_RELEASE_TIMEOUT_US; const uint64_t deadline = microtime() + INPUT_RELEASE_TIMEOUT_US;
if (input->publishedTransport == LGMP_INPUT_TRANSPORT_STREAM)
{
const uint32_t statusSerial = input->statusSerial; const uint32_t statusSerial = input->statusSerial;
LGMP_STATUS status; LGMP_STATUS status;
do do
{ {
status = trySend(input, &message, status = trySend(input, &message, true);
input->publishedTransport, true);
if (status == LGMP_OK) if (status == LGMP_OK)
break; break;
if (!retryableSendStatus(status)) if (!retryableSendStatus(status))
@@ -1135,51 +955,26 @@ static void releaseOnDisconnect(LGMPInput * input)
while (microtime() < deadline); while (microtime() < deadline);
DEBUG_WARN("Timed out waiting for LGMP input stream release"); DEBUG_WARN("Timed out waiting for LGMP input stream release");
return;
}
if (input->publishedTransport != LGMP_INPUT_TRANSPORT_QUEUE)
return;
uint32_t serial = 0;
LGMP_STATUS status;
do
{
status = lgmpClientTrySendData(input->queue,
&message, sizeof(message), &serial);
if (status == LGMP_OK)
break;
if (status != LGMP_ERR_QUEUE_BUSY && status != LGMP_ERR_QUEUE_FULL)
return;
if (input->event)
lgWaitEvent(input->event, INPUT_WORKER_RETRY_MS);
}
while (microtime() < deadline);
if (status != LGMP_OK)
{
DEBUG_WARN("Timed out releasing LGMP input ownership");
return;
}
do
{
uint32_t processed;
status = lgmpClientGetSerial(input->queue, &processed);
if (status != LGMP_OK)
return;
if ((int32_t)(processed - serial) >= 0)
return;
if (input->event)
lgWaitEvent(input->event, INPUT_WORKER_RETRY_MS);
}
while (microtime() < deadline);
DEBUG_WARN("Timed out waiting for LGMP input release");
} }
static int inputThread(void * opaque) static int inputThread(void * opaque)
{ {
LGMPInput * input = opaque; LGMPInput * input = opaque;
LGMPStreamPollState streamPoll;
const LGMP_STATUS pollStatus = lgmpStreamPollInit(&streamPoll,
(struct LGMPStreamPollConfig)
{
.spinCount = 32U,
.minWaitUs = 50U,
.maxWaitUs = 1000U,
});
if (pollStatus != LGMP_OK)
{
DEBUG_ERROR("Failed to initialize LGMP input polling: %s",
lgmpStatusString(pollStatus));
return -1;
}
while (!atomic_load_explicit(&input->stop, memory_order_acquire)) while (!atomic_load_explicit(&input->stop, memory_order_acquire))
{ {
struct LGMPInputCounters stats = { 0 }; struct LGMPInputCounters stats = { 0 };
@@ -1187,13 +982,9 @@ static int inputThread(void * opaque)
bool wake = false; bool wake = false;
LG_LOCK(input->lock); LG_LOCK(input->lock);
processInputStatus(input, &wake); processInputStatus(input, &wake);
flushPending(input); const bool streamProgress = flushPending(input);
pollAckProbe(input);
const uint64_t now = microtime(); const uint64_t now = microtime();
if (!input->stats.probeOutstanding &&
now >= input->stats.nextProbe)
input->stats.probeDue = true;
if (input->connected && input->claimed && !inputStateHeld(input) && if (input->connected && input->claimed && !inputStateHeld(input) &&
!input->pendingCount && !input->pendingCount &&
input->publishedGeneration == input->generation && input->publishedGeneration == input->generation &&
@@ -1207,14 +998,13 @@ static int inputThread(void * opaque)
now - input->lastSend >= INPUT_KEEPALIVE_US) now - input->lastSend >= INPUT_KEEPALIVE_US)
{ {
const KVMFRInputPayload payload = { 0 }; const KVMFRInputPayload payload = { 0 };
queuePayload(input, KVMFR_INPUT_MESSAGE_KEEPALIVE, submitPayload(input, KVMFR_INPUT_MESSAGE_KEEPALIVE,
&payload, false, &wake); &payload, false, &wake);
} }
if (input->pendingCount) if (input->pendingCount)
timeout = INPUT_WORKER_RETRY_MS; timeout = INPUT_WORKER_RETRY_MS;
if (input->stats.probeOutstanding) const bool streamPending = input->pendingCount != 0;
timeout = INPUT_WORKER_RETRY_MS;
const bool report = collectStats(input, now, false, &stats); const bool report = collectStats(input, now, false, &stats);
LG_UNLOCK(input->lock); LG_UNLOCK(input->lock);
@@ -1226,7 +1016,25 @@ static int inputThread(void * opaque)
if (atomic_load_explicit(&input->stop, memory_order_acquire)) if (atomic_load_explicit(&input->stop, memory_order_acquire))
break; break;
lgWaitEvent(input->event, timeout);
bool signaled;
if (streamPending)
{
if (streamProgress)
lgmpStreamPollActivity(&streamPoll);
const uint32_t waitUs = lgmpStreamPollIdle(&streamPoll);
if (!waitUs)
continue;
signaled = lgWaitEventNS(input->event, waitUs * 1000U);
}
else
{
if (streamProgress)
lgmpStreamPollActivity(&streamPoll);
signaled = lgWaitEvent(input->event, timeout);
}
if (signaled)
lgmpStreamPollActivity(&streamPoll);
} }
struct LGMPInputCounters stats = { 0 }; struct LGMPInputCounters stats = { 0 };
@@ -1286,7 +1094,7 @@ bool lgmpInput_connect(LGMPInput * input, uint32_t clientID)
if (status != LGMP_OK) if (status != LGMP_OK)
{ {
LG_UNLOCK(input->lock); LG_UNLOCK(input->lock);
DEBUG_WARN("Failed to subscribe to LGMP input queue: %s", DEBUG_WARN("Failed to subscribe to LGMP input status queue: %s",
lgmpStatusString(status)); lgmpStatusString(status));
return false; return false;
} }
@@ -1307,8 +1115,6 @@ bool lgmpInput_connect(LGMPInput * input, uint32_t clientID)
input->pendingCount = 0; input->pendingCount = 0;
input->clientID = clientID; input->clientID = clientID;
input->capabilities = 0; input->capabilities = 0;
input->transports = 0;
input->streamGeneration = 0;
input->streamEndpointBound = false; input->streamEndpointBound = false;
memset(&input->streamEndpoint, 0, memset(&input->streamEndpoint, 0,
sizeof(input->streamEndpoint)); sizeof(input->streamEndpoint));
@@ -1321,15 +1127,11 @@ bool lgmpInput_connect(LGMPInput * input, uint32_t clientID)
input->publishedGeneration = 0; input->publishedGeneration = 0;
input->publishedSequence = 0; input->publishedSequence = 0;
input->publishedClaimed = false; input->publishedClaimed = false;
input->publishedTransport = LGMP_INPUT_TRANSPORT_NONE;
input->activeTransport = LGMP_INPUT_TRANSPORT_NONE;
input->queueOnly = false;
input->lastSend = 0; input->lastSend = 0;
input->lastInput = 0; input->lastInput = 0;
input->generation = 0; input->generation = 0;
memset(&input->stats, 0, sizeof(input->stats)); memset(&input->stats, 0, sizeof(input->stats));
input->stats.lastReport = microtime(); input->stats.lastReport = microtime();
input->stats.probeDue = true;
clearInputState(input); clearInputState(input);
atomic_store_explicit(&input->stop, false, memory_order_release); atomic_store_explicit(&input->stop, false, memory_order_release);
LGThread * thread; LGThread * thread;
@@ -1372,8 +1174,6 @@ void lgmpInput_disconnect(LGMPInput * input)
input->available = false; input->available = false;
input->ownerBlocked = false; input->ownerBlocked = false;
input->capabilities = 0; input->capabilities = 0;
input->transports = 0;
input->streamGeneration = 0;
input->streamEndpointBound = false; input->streamEndpointBound = false;
memset(&input->streamEndpoint, 0, memset(&input->streamEndpoint, 0,
sizeof(input->streamEndpoint)); sizeof(input->streamEndpoint));
@@ -1407,9 +1207,6 @@ void lgmpInput_disconnect(LGMPInput * input)
input->thread = NULL; input->thread = NULL;
input->event = NULL; input->event = NULL;
input->publishedClaimed = false; input->publishedClaimed = false;
input->publishedTransport = LGMP_INPUT_TRANSPORT_NONE;
input->activeTransport = LGMP_INPUT_TRANSPORT_NONE;
input->queueOnly = false;
LG_UNLOCK(input->lock); LG_UNLOCK(input->lock);
if (queue) if (queue)
@@ -1418,7 +1215,7 @@ void lgmpInput_disconnect(LGMPInput * input)
if (status != LGMP_OK && status != LGMP_ERR_INVALID_SESSION && if (status != LGMP_OK && status != LGMP_ERR_INVALID_SESSION &&
status != LGMP_ERR_QUEUE_TIMEOUT && status != LGMP_ERR_QUEUE_TIMEOUT &&
status != LGMP_ERR_QUEUE_UNSUBSCRIBED) status != LGMP_ERR_QUEUE_UNSUBSCRIBED)
DEBUG_WARN("Failed to unsubscribe from LGMP input queue: %s", DEBUG_WARN("Failed to unsubscribe from LGMP input status queue: %s",
lgmpStatusString(status)); lgmpStatusString(status));
} }
if (event) if (event)
@@ -1537,7 +1334,7 @@ static bool updateKey(void * opaque, int key, bool pressed)
buildKeyboardPayload(input, &payload); buildKeyboardPayload(input, &payload);
const uint64_t overflowFailures = const uint64_t overflowFailures =
input->stats.counters.discreteOverflowFailures; input->stats.counters.discreteOverflowFailures;
bool result = queuePayload(input, KVMFR_INPUT_MESSAGE_KEYBOARD, bool result = submitPayload(input, KVMFR_INPUT_MESSAGE_KEYBOARD,
&payload, false, &wake); &payload, false, &wake);
if (!result && !pressed && input->connected) if (!result && !pressed && input->connected)
{ {
@@ -1588,7 +1385,7 @@ static bool queueMouse(LGMPInput * input, enum LGMPInputMouseMode mode,
payload.mouseRelative.wheel = wheel; payload.mouseRelative.wheel = wheel;
} }
return queuePayload(input, type, &payload, pureMotion, wake); return submitPayload(input, type, &payload, pureMotion, wake);
} }
static bool inputMouseMotion(void * opaque, int32_t x, int32_t y) static bool inputMouseMotion(void * opaque, int32_t x, int32_t y)

View File

@@ -94,10 +94,9 @@ enum
typedef uint32_t KVMFRInputMessageType; typedef uint32_t KVMFRInputMessageType;
/* /*
* One activation must remain on one ordered transport. CLAIM is sequence one, * Input messages are carried by the client's ordered stream endpoint. CLAIM
* reports, KEEPALIVE and RESET increment the sequence, and RELEASE is the * is sequence one, reports, KEEPALIVE and RESET increment the sequence, and
* final record. A sender must start a new generation instead of moving an * RELEASE is the final record.
* active sequence between the queue fallback and a stream endpoint.
*/ */
typedef struct KVMFRInputMessage typedef struct KVMFRInputMessage
{ {
@@ -129,14 +128,6 @@ enum
typedef uint32_t KVMFRInputStatusFlags; typedef uint32_t KVMFRInputStatusFlags;
enum
{
KVMFR_INPUT_TRANSPORT_QUEUE = 0x1,
KVMFR_INPUT_TRANSPORT_STREAM = 0x2,
};
typedef uint32_t KVMFRInputTransportFlags;
enum enum
{ {
KVMFR_INPUT_STREAM_ENDPOINT_AVAILABLE = 0x1, KVMFR_INPUT_STREAM_ENDPOINT_AVAILABLE = 0x1,
@@ -160,6 +151,10 @@ typedef struct KVMFRInputStreamEndpoint
} }
KVMFRInputStreamEndpoint; KVMFRInputStreamEndpoint;
/*
* Published through the LGMP input queue for status and stream discovery.
* KVMFRInputMessage records are carried only by the bound stream endpoint.
*/
typedef struct KVMFRInputStatus typedef struct KVMFRInputStatus
{ {
uint32_t version; uint32_t version;
@@ -171,11 +166,10 @@ typedef struct KVMFRInputStatus
// Input ownership lease duration in milliseconds. // Input ownership lease duration in milliseconds.
uint32_t lease; uint32_t lease;
uint32_t maxButtons; uint32_t maxButtons;
KVMFRInputTransportFlags transports;
uint32_t streamVersion; uint32_t streamVersion;
uint32_t streamEndpointCount; uint32_t streamEndpointCount;
uint32_t streamGeneration; uint32_t streamGeneration;
uint32_t streamReserved[4]; uint32_t streamReserved[5];
KVMFRInputStreamEndpoint streamEndpoint[ KVMFRInputStreamEndpoint streamEndpoint[
KVMFR_INPUT_STREAM_ENDPOINT_COUNT]; KVMFR_INPUT_STREAM_ENDPOINT_COUNT];
} }
@@ -196,14 +190,14 @@ static_assert(sizeof(KVMFRInputMessage) == 32,
"KVMFR input message layout changed"); "KVMFR input message layout changed");
static_assert(sizeof(KVMFRInputStreamEndpoint) == 48, static_assert(sizeof(KVMFRInputStreamEndpoint) == 48,
"KVMFR input stream endpoint layout changed"); "KVMFR input stream endpoint layout changed");
static_assert(offsetof(KVMFRInputStatus, transports) == 32, static_assert(offsetof(KVMFRInputStatus, streamVersion) == 32,
"KVMFR input status transport layout changed"); "KVMFR input status stream layout changed");
static_assert(offsetof(KVMFRInputStatus, streamEndpoint) == 64, static_assert(offsetof(KVMFRInputStatus, streamEndpoint) == 64,
"KVMFR input stream discovery layout changed"); "KVMFR input stream discovery layout changed");
static_assert(sizeof(KVMFRInputStatus) == 448, static_assert(sizeof(KVMFRInputStatus) == 448,
"KVMFR input status layout changed"); "KVMFR input status layout changed");
static_assert(sizeof(KVMFRInputMessage) <= 64, static_assert(sizeof(KVMFRInputMessage) <= KVMFR_INPUT_STREAM_SLOT_SIZE,
"KVMFR input message must fit in one LGMP control message"); "KVMFR input message must fit in one stream record");
#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L #elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
_Static_assert(sizeof(KVMFRInputMouseRelative) == 16, _Static_assert(sizeof(KVMFRInputMouseRelative) == 16,
"KVMFR relative mouse input layout changed"); "KVMFR relative mouse input layout changed");
@@ -219,14 +213,14 @@ _Static_assert(sizeof(KVMFRInputMessage) == 32,
"KVMFR input message layout changed"); "KVMFR input message layout changed");
_Static_assert(sizeof(KVMFRInputStreamEndpoint) == 48, _Static_assert(sizeof(KVMFRInputStreamEndpoint) == 48,
"KVMFR input stream endpoint layout changed"); "KVMFR input stream endpoint layout changed");
_Static_assert(offsetof(KVMFRInputStatus, transports) == 32, _Static_assert(offsetof(KVMFRInputStatus, streamVersion) == 32,
"KVMFR input status transport layout changed"); "KVMFR input status stream layout changed");
_Static_assert(offsetof(KVMFRInputStatus, streamEndpoint) == 64, _Static_assert(offsetof(KVMFRInputStatus, streamEndpoint) == 64,
"KVMFR input stream discovery layout changed"); "KVMFR input stream discovery layout changed");
_Static_assert(sizeof(KVMFRInputStatus) == 448, _Static_assert(sizeof(KVMFRInputStatus) == 448,
"KVMFR input status layout changed"); "KVMFR input status layout changed");
_Static_assert(sizeof(KVMFRInputMessage) <= 64, _Static_assert(sizeof(KVMFRInputMessage) <= KVMFR_INPUT_STREAM_SLOT_SIZE,
"KVMFR input message must fit in one LGMP control message"); "KVMFR input message must fit in one stream record");
#endif #endif
#endif #endif

View File

@@ -64,10 +64,10 @@ static bool IsZero(const void * data, size_t size)
return true; return true;
} }
static bool ArmPollTimer(HANDLE timer, bool active) static bool ArmPollTimer(HANDLE timer, uint32_t waitUs)
{ {
LARGE_INTEGER due = {}; LARGE_INTEGER due = {};
due.QuadPart = active ? -2500 : -10000; due.QuadPart = -static_cast<LONGLONG>(waitUs) * 10;
return SetWaitableTimer(timer, &due, 0, nullptr, nullptr, FALSE) != FALSE; return SetWaitableTimer(timer, &due, 0, nullptr, nullptr, FALSE) != FALSE;
} }
@@ -199,7 +199,11 @@ bool CLGMPInputTransport::ReconcileStreams(bool& changed)
const LGMP_STATUS status = lgmpHostStreamUnbind(endpoint.stream); const LGMP_STATUS status = lgmpHostStreamUnbind(endpoint.stream);
if (status == LGMP_ERR_STREAM_BUSY) if (status == LGMP_ERR_STREAM_BUSY)
{
// A vanished peer may have left an active reservation. Keep this
// endpoint draining; force-unbind is reserved for transport teardown.
continue; continue;
}
if (status != LGMP_OK && status != LGMP_ERR_STREAM_UNBOUND) if (status != LGMP_OK && status != LGMP_ERR_STREAM_UNBOUND)
{ {
DEBUG_ERROR("lgmpHostStreamUnbind Failed (Input): %s", DEBUG_ERROR("lgmpHostStreamUnbind Failed (Input): %s",
@@ -294,7 +298,11 @@ void CLGMPInputTransport::ResetStreams()
endpoint.draining = false; endpoint.draining = false;
} }
else if (status == LGMP_ERR_STREAM_BUSY) else if (status == LGMP_ERR_STREAM_BUSY)
{
// Stop is restartable, so preserve outstanding records and finish
// the graceful unbind after the transport starts again.
endpoint.draining = true; endpoint.draining = true;
}
changed = true; changed = true;
} }
if (changed) if (changed)
@@ -371,7 +379,8 @@ bool CLGMPInputTransport::PublishStatus()
KVMFR_INPUT_CAP_MOUSE_RELATIVE | KVMFR_INPUT_CAP_MOUSE_RELATIVE |
KVMFR_INPUT_CAP_MOUSE_ABSOLUTE | KVMFR_INPUT_CAP_MOUSE_ABSOLUTE |
KVMFR_INPUT_CAP_KEYBOARD : 0; KVMFR_INPUT_CAP_KEYBOARD : 0;
status.flags = available ? KVMFR_INPUT_STATUS_AVAILABLE : 0; status.flags = available ?
KVMFR_INPUT_STATUS_AVAILABLE : 0;
if (m_targetState.owned) if (m_targetState.owned)
{ {
status.flags |= KVMFR_INPUT_STATUS_HAS_OWNER; status.flags |= KVMFR_INPUT_STATUS_HAS_OWNER;
@@ -381,8 +390,6 @@ bool CLGMPInputTransport::PublishStatus()
status.generation = m_endpointGeneration; status.generation = m_endpointGeneration;
status.lease = static_cast<uint32_t>(OWNER_LEASE_MS); status.lease = static_cast<uint32_t>(OWNER_LEASE_MS);
status.maxButtons = KVMFR_INPUT_MOUSE_BUTTON_COUNT; status.maxButtons = KVMFR_INPUT_MOUSE_BUTTON_COUNT;
status.transports = KVMFR_INPUT_TRANSPORT_QUEUE |
KVMFR_INPUT_TRANSPORT_STREAM;
status.streamVersion = KVMFR_INPUT_STREAM_VERSION; status.streamVersion = KVMFR_INPUT_STREAM_VERSION;
status.streamEndpointCount = KVMFR_INPUT_STREAM_ENDPOINT_COUNT; status.streamEndpointCount = KVMFR_INPUT_STREAM_ENDPOINT_COUNT;
status.streamGeneration = m_streamGeneration; status.streamGeneration = m_streamGeneration;
@@ -535,7 +542,6 @@ void CLGMPInputTransport::Stop()
m_ownerClientID = 0; m_ownerClientID = 0;
m_ownerGeneration = 0; m_ownerGeneration = 0;
m_ownerSequence = 0; m_ownerSequence = 0;
m_ownerTransport = MessageTransport::NONE;
m_ownerDeadline = 0; m_ownerDeadline = 0;
ResetStreams(); ResetStreams();
{ {
@@ -553,8 +559,7 @@ bool CLGMPInputTransport::IsOwner(
} }
bool CLGMPInputTransport::Claim( bool CLGMPInputTransport::Claim(
uint32_t sourceClientID, const KVMFRInputMessage& message, uint32_t sourceClientID, const KVMFRInputMessage& message)
MessageTransport transport)
{ {
if (message.sequence != 1) if (message.sequence != 1)
{ {
@@ -581,7 +586,6 @@ bool CLGMPInputTransport::Claim(
m_ownerClientID = sourceClientID; m_ownerClientID = sourceClientID;
m_ownerGeneration = message.generation; m_ownerGeneration = message.generation;
m_ownerSequence = message.sequence; m_ownerSequence = message.sequence;
m_ownerTransport = transport;
RenewLease(); RenewLease();
UpdateTargetState(m_target->GetState(source)); UpdateTargetState(m_target->GetState(source));
++m_statistics.claims; ++m_statistics.claims;
@@ -607,7 +611,6 @@ void CLGMPInputTransport::ReleaseOwner(bool reset)
m_ownerClientID = 0; m_ownerClientID = 0;
m_ownerGeneration = 0; m_ownerGeneration = 0;
m_ownerSequence = 0; m_ownerSequence = 0;
m_ownerTransport = MessageTransport::NONE;
m_ownerDeadline = 0; m_ownerDeadline = 0;
++m_statistics.releases; ++m_statistics.releases;
} }
@@ -617,7 +620,7 @@ void CLGMPInputTransport::CheckOwner()
if (!m_target) if (!m_target)
return; return;
if (m_ownerClientID && m_ownerTransport == MessageTransport::STREAM) if (m_ownerClientID)
{ {
bool found = false; bool found = false;
bool retiring = false; bool retiring = false;
@@ -698,8 +701,7 @@ bool CLGMPInputTransport::ValidatePayload(
} }
bool CLGMPInputTransport::ProcessMessage( bool CLGMPInputTransport::ProcessMessage(
uint32_t sourceClientID, const KVMFRInputMessage& message, uint32_t sourceClientID, const KVMFRInputMessage& message)
MessageTransport transport)
{ {
const bool owner = IsOwner(sourceClientID, message.generation); const bool owner = IsOwner(sourceClientID, message.generation);
InputSourceId source; InputSourceId source;
@@ -728,15 +730,14 @@ bool CLGMPInputTransport::ProcessMessage(
++m_statistics.nonOwner; ++m_statistics.nonOwner;
return true; return true;
} }
if (message.sequence == 1 && m_ownerSequence == 1 && if (message.sequence == 1 && m_ownerSequence == 1)
m_ownerTransport == transport)
return true; return true;
++m_statistics.sequenceErrors; ++m_statistics.sequenceErrors;
ReleaseOwner(true); ReleaseOwner(true);
return false; return false;
} }
return Claim(sourceClientID, message, transport); return Claim(sourceClientID, message);
} }
if (!owner) if (!owner)
@@ -745,13 +746,6 @@ bool CLGMPInputTransport::ProcessMessage(
return true; return true;
} }
if (m_ownerTransport != transport)
{
++m_statistics.sequenceErrors;
ReleaseOwner(true);
return false;
}
const uint32_t expectedSequence = Seq::Next(m_ownerSequence); const uint32_t expectedSequence = Seq::Next(m_ownerSequence);
if (message.sequence != expectedSequence) if (message.sequence != expectedSequence)
{ {
@@ -823,57 +817,6 @@ bool CLGMPInputTransport::ProcessMessage(
return true; return true;
} }
bool CLGMPInputTransport::DrainQueueMessages(bool& received)
{
received = false;
unsigned count = 0;
for (; count < DRAIN_LIMIT; ++count)
{
uint8_t data[LGMP_MSGS_SIZE] = {};
size_t size = 0;
uint32_t sourceClientID = 0;
const LGMP_STATUS status = lgmpHostReadDataWithSource(
m_queue, data, &size, &sourceClientID);
if (status == LGMP_ERR_QUEUE_EMPTY)
break;
if (status != LGMP_OK)
{
DEBUG_ERROR("lgmpHostReadData Failed (Input): %s",
lgmpStatusString(status));
return false;
}
received = true;
++m_statistics.messages;
if (size != sizeof(KVMFRInputMessage))
{
DEBUG_WARN("Ignoring invalid KVMFR input message size");
++m_statistics.malformedSize;
if (sourceClientID == m_ownerClientID)
ReleaseOwner(true);
}
else
{
KVMFRInputMessage message = {};
memcpy(&message, data, sizeof(message));
ProcessMessage(sourceClientID, message, MessageTransport::QUEUE);
}
const LGMP_STATUS ackStatus = lgmpHostAckData(m_queue);
if (ackStatus != LGMP_OK)
{
DEBUG_ERROR("lgmpHostAckData Failed (Input): %s",
lgmpStatusString(ackStatus));
return false;
}
}
if (count > m_statistics.maxDrain)
m_statistics.maxDrain = count;
if (count == DRAIN_LIMIT)
++m_statistics.drainLimit;
return true;
}
bool CLGMPInputTransport::DrainStreamMessages(bool& received) bool CLGMPInputTransport::DrainStreamMessages(bool& received)
{ {
received = false; received = false;
@@ -930,8 +873,7 @@ bool CLGMPInputTransport::DrainStreamMessages(bool& received)
{ {
KVMFRInputMessage message = {}; KVMFRInputMessage message = {};
memcpy(&message, buffer.data, sizeof(message)); memcpy(&message, buffer.data, sizeof(message));
ProcessMessage(selected->clientID, message, ProcessMessage(selected->clientID, message);
MessageTransport::STREAM);
} }
} }
@@ -1008,7 +950,24 @@ void CLGMPInputTransport::Thread()
DEBUG_WARN("Failed to raise input MMCSS priority: %lu", DEBUG_WARN("Failed to raise input MMCSS priority: %lu",
GetLastError()); GetLastError());
ULONGLONG activeUntil = 0; LGMPStreamPollState streamPoll = {};
LGMPStreamPollConfig pollConfig = {};
pollConfig.spinCount = 64U;
pollConfig.minWaitUs = 25U;
pollConfig.maxWaitUs = 1000U;
const LGMP_STATUS pollStatus = lgmpStreamPollInit(&streamPoll,
pollConfig);
if (pollStatus != LGMP_OK)
{
DEBUG_ERROR("Failed to initialize LGMP input polling: %s",
lgmpStatusString(pollStatus));
if (m_target)
m_target->Failed();
if (avTaskHandle)
AvRevertMmThreadCharacteristics(avTaskHandle);
return;
}
m_statistics = {}; m_statistics = {};
m_statistics.lastLog = GetTickCount64(); m_statistics.lastLog = GetTickCount64();
const HANDLE waitHandles[] = { m_stopEvent, m_pollTimer }; const HANDLE waitHandles[] = { m_stopEvent, m_pollTimer };
@@ -1017,26 +976,7 @@ void CLGMPInputTransport::Thread()
{ {
CheckOwner(); CheckOwner();
bool streamReceived = false; bool streamReceived = false;
bool queueReceived = false; if (!DrainStreamMessages(streamReceived))
bool drained = true;
// Do not consume the other lane while the current owner still holds its
// lane. Its RELEASE is the ordering barrier for a subsequent generation.
if (m_ownerTransport == MessageTransport::QUEUE)
{
drained = DrainQueueMessages(queueReceived);
if (drained && m_ownerTransport != MessageTransport::QUEUE)
drained = DrainStreamMessages(streamReceived);
}
else if (m_ownerTransport == MessageTransport::STREAM)
{
drained = DrainStreamMessages(streamReceived);
if (drained && m_ownerTransport != MessageTransport::STREAM)
drained = DrainQueueMessages(queueReceived);
}
else
drained = DrainStreamMessages(streamReceived) &&
DrainQueueMessages(queueReceived);
if (!drained)
{ {
failed = true; failed = true;
break; break;
@@ -1047,13 +987,15 @@ void CLGMPInputTransport::Thread()
failed = true; failed = true;
break; break;
} }
if (streamReceived)
lgmpStreamPollActivity(&streamPoll);
const ULONGLONG now = GetTickCount64(); const ULONGLONG now = GetTickCount64();
if (streamReceived || queueReceived)
activeUntil = now + ACTIVE_POLL_MS;
LogStatistics(now); LogStatistics(now);
const bool active = now < activeUntil; const uint32_t waitUs = lgmpStreamPollIdle(&streamPoll);
if (!ArmPollTimer(m_pollTimer, active)) if (!waitUs)
continue;
if (!ArmPollTimer(m_pollTimer, waitUs))
{ {
DEBUG_ERROR_HR(GetLastError(), "Failed to arm LGMP input timer"); DEBUG_ERROR_HR(GetLastError(), "Failed to arm LGMP input timer");
if (WaitForSingleObject(m_stopEvent, 1) != WAIT_TIMEOUT) if (WaitForSingleObject(m_stopEvent, 1) != WAIT_TIMEOUT)

View File

@@ -42,17 +42,9 @@ class CLGMPInputTransport final : public IInputSource
{ {
private: private:
static constexpr ULONGLONG OWNER_LEASE_MS = 500; static constexpr ULONGLONG OWNER_LEASE_MS = 500;
static constexpr ULONGLONG ACTIVE_POLL_MS = 50;
static constexpr ULONGLONG LOG_INTERVAL_MS = 5000; static constexpr ULONGLONG LOG_INTERVAL_MS = 5000;
static constexpr unsigned DRAIN_LIMIT = 256; static constexpr unsigned DRAIN_LIMIT = 256;
enum class MessageTransport : uint8_t
{
NONE,
QUEUE,
STREAM,
};
struct StreamEndpoint struct StreamEndpoint
{ {
PLGMPHostStream stream; PLGMPHostStream stream;
@@ -96,7 +88,6 @@ private:
uint32_t m_ownerClientID = 0; uint32_t m_ownerClientID = 0;
uint32_t m_ownerGeneration = 0; uint32_t m_ownerGeneration = 0;
uint32_t m_ownerSequence = 0; uint32_t m_ownerSequence = 0;
MessageTransport m_ownerTransport = MessageTransport::NONE;
ULONGLONG m_ownerDeadline = 0; ULONGLONG m_ownerDeadline = 0;
InputTargetState m_targetState; InputTargetState m_targetState;
uint32_t m_endpointGeneration = 0; uint32_t m_endpointGeneration = 0;
@@ -116,14 +107,13 @@ private:
bool PublishStatus(); bool PublishStatus();
void FlushStatus(); void FlushStatus();
void LogStatistics(ULONGLONG now); void LogStatistics(ULONGLONG now);
bool DrainQueueMessages(bool& received);
bool DrainStreamMessages(bool& received); bool DrainStreamMessages(bool& received);
bool ProcessMessage(uint32_t sourceClientID, bool ProcessMessage(uint32_t sourceClientID,
const KVMFRInputMessage& message, MessageTransport transport); const KVMFRInputMessage& message);
bool ValidatePayload(const KVMFRInputMessage& message) const; bool ValidatePayload(const KVMFRInputMessage& message) const;
bool IsOwner(uint32_t sourceClientID, uint32_t generation) const; bool IsOwner(uint32_t sourceClientID, uint32_t generation) const;
bool Claim(uint32_t sourceClientID, bool Claim(uint32_t sourceClientID,
const KVMFRInputMessage& message, MessageTransport transport); const KVMFRInputMessage& message);
void RenewLease(); void RenewLease();
void ReleaseOwner(bool reset); void ReleaseOwner(bool reset);
void CheckOwner(); void CheckOwner();