mirror of
https://github.com/arehnman/yttrium-virtio-gpu.git
synced 2026-10-31 02:05:04 +00:00
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]>
346 lines
8.7 KiB
C++
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;
|
|
};
|