Files
yttrium-virtio-gpu/NetKVM/Common/ParaNdis_LockFreeQueue.h
T
Konstantin Kostiuk e6546dc358 RHEL-65784: Fix files ending
clang-format always check one \n at the end of file.

Jira: https://issues.redhat.com/browse/RHEL-65784

Signed-off-by: Konstantin Kostiuk <[email protected]>
2024-11-20 15:48:35 +02:00

346 lines
8.7 KiB
C++

#pragma once
/*
* Multiple producer single consumer lock free queue implementation
*
* This implementation of lock free queue is based on the freebsd implementation
* of a lock free queue which can be found in:
* https://svnweb.freebsd.org/base/release/8.0.0/sys/sys/buf_ring.h?view=markup
* https://svnweb.freebsd.org/base/release/8.0.0/sys/kern/subr_bufring.c?view=markup
*
* Copyright Red Hat, Inc. 2017
*
* Authors:
* Sameeh Jubran <[email protected]>
*
*/
template <typename TEntryType>
class CLockFreeQueue
{
public:
CLockFreeQueue() :
m_Context(nullptr),
m_ProducerSize(0),
m_ConsumerSize(0),
m_ProducerMask(0),
m_ConsumerMask(0),
m_ProducerHead(0),
m_ConsumerHead(0),
m_ProducerTail(0),
m_ConsumerTail(0),
m_PQueueRing(nullptr)
{
}
BOOLEAN Create(PPARANDIS_ADAPTER pContext, INT size)
{
m_Context = pContext;
m_ProducerSize = size;
m_ConsumerSize = size;
m_ProducerMask = size - 1;
m_ConsumerMask = size - 1;
/*
* The size of the queue has to be a power of two in order to easily
* implement the overflow cyclic check
*/
if (!IsPowerOfTwo(size))
{
return FALSE;
}
m_PQueueRing = (TEntryType **) ParaNdis_AllocateMemory(pContext, sizeof(TEntryType *) * size);
if (m_PQueueRing == nullptr)
{
return FALSE;
}
return TRUE;
}
~CLockFreeQueue()
{
if (m_PQueueRing != nullptr)
{
NdisFreeMemory(m_PQueueRing, 0, 0);
m_PQueueRing = nullptr;
}
}
/*
* multi-producer safe lock-free ring buffer enqueue
*/
bool Enqueue(TEntryType *entry)
{
LONG producer_head, producer_next, consumer_tail;
/* Critical section */
{
do {
producer_head = m_ProducerHead;
producer_next = (producer_head + 1) & m_ProducerMask;
consumer_tail = m_ConsumerTail;
if (producer_next == consumer_tail) {
return FALSE;
}
} while (InterlockedCompareExchange(&m_ProducerHead, producer_next, producer_head) != producer_head);
m_PQueueRing[producer_head] = entry;
KeMemoryBarrier();
/*
* If there are other enqueues in progress
* that preceded us, we need to wait for them
* to complete
*/
while (m_ProducerTail != producer_head)
{}
m_ProducerTail = producer_next;
}
return TRUE;
}
/*
* single-consumer dequeue
* should be called under lock!
*/
TEntryType *Dequeue()
{
LONG consumer_head, consumer_next;
volatile LONG producer_tail;
TEntryType *entry;
consumer_head = m_ConsumerHead;
producer_tail = m_ProducerTail;
consumer_next = (consumer_head + 1) & m_ConsumerMask;
if (consumer_head == producer_tail)
{
return nullptr;
}
m_ConsumerHead = consumer_next;
entry = m_PQueueRing[consumer_head];
m_ConsumerTail = consumer_next;
return entry;
}
TEntryType *DequeueMC()
{
LONG consumer_head, consumer_next, producer_tail;
TEntryType *entry;
/* Critical section */
{
do {
consumer_head = m_ConsumerHead;
producer_tail = m_ProducerTail;
consumer_next = (consumer_head + 1) & m_ConsumerMask;
if (consumer_head == producer_tail) {
return nullptr;
}
} while (InterlockedCompareExchange(&m_ConsumerHead, consumer_next, consumer_head) != consumer_head);
entry = m_PQueueRing[consumer_head];
KeMemoryBarrier();
/*
* If there are other enqueues in progress
* that preceded us, we need to wait for them
* to complete
*/
while (m_ConsumerTail != consumer_head)
{
}
m_ConsumerTail = consumer_next;
}
return entry;
}
/*
* single-consumer peek operation
* should be called under lock!
*/
TEntryType *Peek()
{
if (m_ConsumerHead == m_ProducerTail)
{
return nullptr;
}
return m_PQueueRing[m_ConsumerHead];
}
BOOLEAN IsEmpty()
{
return (m_ConsumerHead == m_ProducerTail);
}
BOOLEAN IsFull()
{
return (((m_ProducerHead + 1) & m_ProducerMask) == m_ConsumerTail);
}
private:
volatile LONG m_ProducerHead;
volatile LONG m_ProducerTail;
INT m_ProducerSize;
INT m_ProducerMask;
volatile LONG m_ConsumerHead;
volatile LONG m_ConsumerTail;
INT m_ConsumerSize;
INT m_ConsumerMask;
TEntryType **m_PQueueRing;
PPARANDIS_ADAPTER m_Context;
};
// NOTE1: Calls to Dequeue() and Peek()
// must be externally synchronized!
// Peek returns object that valid till you're in context
// where Dequeue() can't change the queue state
// IsEmpty() returns only momentary state that can be
// changed at eny time by Dequeue or Enqueue()
// NOTE2: Enqueue() is not synchronized with anything
// and can be used simultaneously from various contexts
template <typename TEntryType>
class CLockFreeDynamicQueue : public CPlacementAllocatable
{
public:
CLockFreeDynamicQueue() : m_QueueFullListIsEmpty(TRUE), m_Size(0), m_ElementCount(0)
{}
BOOLEAN Create(PPARANDIS_ADAPTER pContext, INT size)
{
m_QueueFullListIsEmpty = TRUE;
m_Size = size;
m_ElementCount = 0;
return m_Queue.Create(pContext, size);
}
// Multiple Producer Safe Enqueue
void Enqueue(TEntryType * entry)
{
InterlockedIncrement(&m_ElementCount);
if (!m_QueueFullListIsEmpty || !m_Queue.Enqueue(entry))
{
TPassiveSpinLocker LockedContext(m_QueueFullListLock);
m_QueueFullList.PushBack(entry);
InterlockedExchange(&m_QueueFullListIsEmpty, m_QueueFullList.IsEmpty());
}
}
// Note: This procedure must be externally synchronized with
// calls to Peek() and Dequeue(), see note NOTE1
TEntryType *Dequeue()
{
TEntryType * ptr = m_Queue.Dequeue();
if (ptr == nullptr)
{
FillQueue();
ptr = m_Queue.Dequeue();
}
DecrementCount(ptr != nullptr);
return ptr;
}
// Multiple consumer Dequeue
TEntryType *DequeueMC()
{
TEntryType * ptr = m_Queue.DequeueMC();
if (ptr == nullptr)
{
FillQueue();
ptr = m_Queue.DequeueMC();
}
DecrementCount(ptr != nullptr);
return ptr;
}
// Note: This procedure must be externally synchronized with
// calls to Peek() and Dequeue(), see note NOTE1
TEntryType *Peek()
{
TEntryType * element = m_Queue.Peek();
if (element == nullptr && !m_QueueFullListIsEmpty)
{
FillQueue();
element = m_Queue.Peek();
}
return element;
}
// This procedure is for informational purpose only
// see note NOTE1
BOOLEAN IsEmpty()
{
return m_Queue.IsEmpty() ? (BOOLEAN) m_QueueFullListIsEmpty : FALSE;
}
private:
void DecrementCount(BOOLEAN decrement)
{
if (decrement)
{
InterlockedDecrement(&m_ElementCount);
}
}
bool FillQueue()
{
bool res = FALSE;
TEntryType *entry = nullptr;
if (!m_QueueFullListIsEmpty)
{
/* LockedContext Locker will always be locked during all of it's lifetime,
especially when it's being destructed. SDV does not recognize that due to encapsulation
so the warning is suppressed*/
#pragma warning(suppress: 26110)
TPassiveSpinLocker LockedContext(m_QueueFullListLock);
do
{
entry = m_QueueFullList.Pop();
if (entry != nullptr)
{
res = m_Queue.Enqueue(entry);
if (!res)
{
m_QueueFullList.Push(entry);
}
} else
{
break;
}
} while (res);
InterlockedExchange(&m_QueueFullListIsEmpty, m_QueueFullList.IsEmpty());
}
return res;
}
CLockFreeQueue<TEntryType> m_Queue;
CNdisList<TEntryType, CRawAccess, CNonCountingObject> m_QueueFullList;
CNdisSpinLock m_QueueFullListLock;
volatile LONG m_QueueFullListIsEmpty;
volatile LONG m_ElementCount;
volatile LONG m_Size;
};