Files

166 lines
5.9 KiB
C

// Kernel message queue primitive. This file owns bounded queue storage and queue timeout behavior.
#include <stdint.h>
#include <string.h>
#include <kernel/ke_queue.h>
#include <kernel/ke_sched.h>
#include <kernel/ke_time.h>
#include <kernel/ke_irql.h>
#include <kernel/ob_object.h>
#define QUEUE_TIMEOUT_INFINITE UINT64_MAX
// Destroys a queue and releases its private state events.
static void KiQueueDestroy(OBJECT* obj) {
QUEUE* queue = (QUEUE*)obj;
if (!queue)
return;
if (queue->NotEmptyEvent) {
ObDereferenceObject(&queue->NotEmptyEvent->Header);
queue->NotEmptyEvent = (EVENT*)0;
}
if (queue->NotFullEvent) {
ObDereferenceObject(&queue->NotFullEvent->Header);
queue->NotFullEvent = (EVENT*)0;
}
}
// Updates waitable state after one message has been inserted.
static void KiQueueNoteInsertedLocked(QUEUE* queue, uint32_t previousCount) {
if (!queue || !queue->NotEmptyEvent || !queue->NotFullEvent)
return;
if (previousCount == 0)
KeSignalEvent(queue->NotEmptyEvent);
if (queue->Count == QUEUE_DEPTH)
KeClearEvent(queue->NotFullEvent);
}
// Updates waitable state after one message has been removed.
static void KiQueueNoteRemovedLocked(QUEUE* queue, uint32_t previousCount) {
if (!queue || !queue->NotEmptyEvent || !queue->NotFullEvent)
return;
if (previousCount == QUEUE_DEPTH)
KeSignalEvent(queue->NotFullEvent);
if (queue->Count == 0)
KeClearEvent(queue->NotEmptyEvent);
}
// Computes an absolute monotonic deadline for a finite timeout.
static uint64_t KiQueueTimeoutDeadline(uint64_t timeoutMilliseconds) {
return KeMonotonicDeadlineAfterMilliseconds(timeoutMilliseconds);
}
// Computes the remaining wait time for a finite timeout deadline.
static uint64_t KiQueueRemainingMilliseconds(uint64_t deadlineMicroseconds) {
return KeMonotonicMillisecondsUntil(deadlineMicroseconds);
}
// Waits for a queue state event using either an infinite or finite timeout.
static KSTATUS KiQueueWaitForEvent(QUEUE* queue, EVENT* event, uint64_t timeoutMilliseconds, uint64_t deadlineMicroseconds) {
if (!queue || !event)
return NTLSTATUS_INVALID_PARAMETER;
uint64_t waitMilliseconds = timeoutMilliseconds;
if (timeoutMilliseconds != QUEUE_TIMEOUT_INFINITE) {
waitMilliseconds = KiQueueRemainingMilliseconds(deadlineMicroseconds);
if (waitMilliseconds == 0)
return NTLSTATUS_TIMEOUT;
}
EVENT* events[1] = { event };
return KeWaitForMultipleEvents(events, 1, 0, waitMilliseconds, (uint32_t*)0);
}
// Creates a fixed-depth queue with waitable occupancy and capacity events.
KSTATUS KeQueueCreate(QUEUE** out) {
if (!out) return NTLSTATUS_INVALID_PARAMETER;
*out = (QUEUE*)0;
QUEUE* q = (QUEUE*)ObAllocateObject(ObjectTypeQueue, sizeof(QUEUE));
if (!q) return NTLSTATUS_NO_MEMORY;
q->Header.Destroy = KiQueueDestroy;
q->Lock = (SPINLOCK)SPINLOCK_INIT;
q->Head = q->Tail = q->Count = 0;
KSTATUS s = KeEventCreate(EventManualReset, 0, &q->NotEmptyEvent);
if (!KSUCCESS(s)) {
ObDereferenceObject(&q->Header);
return s;
}
s = KeEventCreate(EventManualReset, 1, &q->NotFullEvent);
if (!KSUCCESS(s)) {
ObDereferenceObject(&q->Header);
return s;
}
*out = q;
return NTLSTATUS_SUCCESS;
}
// Sends one fixed-size queue message, waiting according to the timeout contract.
KSTATUS KeSendMessage(QUEUE* queue, const QUEUE_MESSAGE* msg, uint64_t timeoutMilliseconds) {
if (!queue || !msg) return NTLSTATUS_INVALID_PARAMETER;
if (timeoutMilliseconds != 0 && !KeCanWait()) return NTLSTATUS_INVALID_IRQL;
uint64_t deadlineMicroseconds = 0;
if (timeoutMilliseconds != 0 && timeoutMilliseconds != QUEUE_TIMEOUT_INFINITE)
deadlineMicroseconds = KiQueueTimeoutDeadline(timeoutMilliseconds);
while (true) {
uintptr_t flags = KeAcquireSpinlock(&queue->Lock);
if (queue->Count < QUEUE_DEPTH) {
uint32_t previousCount = queue->Count;
queue->Ring[queue->Tail] = *msg;
queue->Tail = (queue->Tail + 1) % QUEUE_DEPTH;
queue->Count++;
KiQueueNoteInsertedLocked(queue, previousCount);
KeReleaseSpinlock(&queue->Lock, flags);
return NTLSTATUS_SUCCESS;
}
KeClearEvent(queue->NotFullEvent);
KeReleaseSpinlock(&queue->Lock, flags);
if (timeoutMilliseconds == 0)
return NTLSTATUS_WOULD_BLOCK;
KSTATUS s = KiQueueWaitForEvent(queue, queue->NotFullEvent, timeoutMilliseconds, deadlineMicroseconds);
if (s == NTLSTATUS_TIMEOUT || KFAILED(s))
return s;
}
}
// Receives one fixed-size queue message, waiting according to the timeout contract.
KSTATUS KeReceiveMessage(QUEUE* queue, QUEUE_MESSAGE* msg, uint64_t timeoutMilliseconds) {
if (!queue || !msg) return NTLSTATUS_INVALID_PARAMETER;
if (timeoutMilliseconds != 0 && !KeCanWait()) return NTLSTATUS_INVALID_IRQL;
uint64_t deadlineMicroseconds = 0;
if (timeoutMilliseconds != 0 && timeoutMilliseconds != QUEUE_TIMEOUT_INFINITE)
deadlineMicroseconds = KiQueueTimeoutDeadline(timeoutMilliseconds);
while (true) {
uintptr_t flags = KeAcquireSpinlock(&queue->Lock);
if (queue->Count > 0) {
uint32_t previousCount = queue->Count;
*msg = queue->Ring[queue->Head];
queue->Head = (queue->Head + 1) % QUEUE_DEPTH;
queue->Count--;
KiQueueNoteRemovedLocked(queue, previousCount);
KeReleaseSpinlock(&queue->Lock, flags);
return NTLSTATUS_SUCCESS;
}
KeClearEvent(queue->NotEmptyEvent);
KeReleaseSpinlock(&queue->Lock, flags);
if (timeoutMilliseconds == 0)
return NTLSTATUS_WOULD_BLOCK;
KSTATUS s = KiQueueWaitForEvent(queue, queue->NotEmptyEvent, timeoutMilliseconds, deadlineMicroseconds);
if (s == NTLSTATUS_TIMEOUT || KFAILED(s))
return s;
}
}