NetKvm: TX: Use lock free queue

This patch separates the pathes of Windows and DPCs. Prior to this patch
Windows and DPCs shared the same lock (TX m_Lock) even though Windows
needed to send a single NBL and return. Now a multiple producer - single
consumer queue was added between Windows's path and DPC's path where Windows
is the producer and DPC is the Consumer.

The queue's fixed size enforces handling cases where Windows is fast
enough to fill the whole queue, this case was handled by using a list
- m_SendQueueFullList - that is filled on such cases (queue is full)
by Windows and later on dequeued by DPC in the function FillQueue().

Signed-off-by: Sameeh Jubran <[email protected]>
This commit is contained in:
Sameeh Jubran authored and Yan Vugenfirer committed 2017-11-20 14:33:15 +02:00
1 parent 5af4347f9d
commit e8f319fdc0
2 files changed
+122 -48

No files matched your search

+96 -43
View File
@@ -265,6 +265,20 @@ void CNBL::OnLastReferenceGone()
CParaNdisTX::~CParaNdisTX()
{
TSpinLocker LockedContext(m_Lock);
CNBL* NBL = nullptr;
NBL = m_SendQueue.Dequeue();
while (NBL)
{
NBL->~CNBL();
NBL = m_SendQueue.Dequeue();
}
m_SendQueue.~CLockFreeQueue();
DPrintf(1, ("Pools state %d-> NB: %d, NBL: %d\n", m_queueIndex, m_nbPool.GetCount(), m_nblPool.GetCount()));
if (m_StateMachineRegistered)
{
@@ -283,13 +297,17 @@ bool CParaNdisTX::Create(PPARANDIS_ADAPTER Context, UINT DeviceQueueIndex)
m_nbPool.Create(Context->MiniportHandle);
m_nblPool.Create(Context->MiniportHandle);
m_SendQueueFullListIsEmpty = TRUE;
return m_VirtQueue.Create(DeviceQueueIndex,
&m_Context->IODevice,
m_Context->MiniportHandle,
m_Context->bDoPublishIndices ? true : false,
m_Context->maxFreeTxDescriptors,
m_Context->nVirtioHeaderSize,
m_Context);
m_Context) &&
m_SendQueue.Create(Context, m_SendQueue.IsPowerOfTwo(m_Context->maxFreeTxDescriptors) ?
8 * m_Context->maxFreeTxDescriptors : PARANDIS_TX_LOCK_FREE_QUEUE_DEFAULT_SIZE);
}
void CParaNdisTX::CompleteOutstandingNBLChain(PNET_BUFFER_LIST NBL, ULONG Flags)
@@ -373,7 +391,21 @@ void CParaNdisTX::NBLMappingDone(CNBL *NBLHolder)
if (NBLHolder->MappingSucceeded())
{
DoPendingTasks(NBLHolder);
if (!m_SendQueueFullListIsEmpty || !m_SendQueue.Enqueue(NBLHolder))
{
TSpinLocker LockedContext(m_SendQueueFullListLock);
m_SendQueueFullList.PushBack(NBLHolder);
InterlockedExchange(&m_SendQueueFullListIsEmpty, m_SendQueueFullList.IsEmpty());
}
if (m_DpcWaiting == 0)
{
#if NDIS_SUPPORT_NDIS620
NdisMQueueDpcEx(m_Context->InterruptHandle, m_messageIndex, &DPCAffinity, NULL);
#else
NdisMQueueDpc(m_Context->InterruptHandle, 0, 1 << KeGetCurrentProcessorNumber(), NULL);
#endif
}
}
else
{
@@ -471,16 +503,21 @@ PNET_BUFFER_LIST CParaNdisTX::BuildCancelList(PVOID CancelId)
{
PNET_BUFFER_LIST CanceledNBLs = nullptr;
TSpinLocker LockedContext(m_Lock);
CNBL* NBL = nullptr;
m_SendList.ForEachDetachedIf([CancelId](CNBL* NBL){ return NBL->MatchCancelID(CancelId) && !NBL->HaveDetachedBuffers(); },
[this, &CanceledNBLs](CNBL* NBL)
{
NBL->SetStatus(NDIS_STATUS_SEND_ABORTED);
auto RawNBL = NBL->DetachInternalObject();
NBL->Release();
NET_BUFFER_LIST_NEXT_NBL(RawNBL) = CanceledNBLs;
CanceledNBLs = RawNBL;
});
NBL = PopMappedNBL();
do
{
if (NBL->MatchCancelID(CancelId) && !NBL->HaveDetachedBuffers())
{
NBL->SetStatus(NDIS_STATUS_SEND_ABORTED);
auto RawNBL = NBL->DetachInternalObject();
NBL->Release();
NET_BUFFER_LIST_NEXT_NBL(RawNBL) = CanceledNBLs;
CanceledNBLs = RawNBL;
}
NBL = PopMappedNBL();
} while (NBL);
return CanceledNBLs;
}
@@ -517,7 +554,7 @@ bool CParaNdisTX::SendMapped(bool IsInterrupt, CRawCNBLList& toWaitingList)
while (HaveBuffers && HaveMappedNBLs())
{
auto NBLHolder = PopMappedNBL();
auto NBLHolder = PeekMappedToSendNBL();
if (NBLHolder->HaveMappedBuffers())
{
@@ -528,7 +565,6 @@ bool CParaNdisTX::SendMapped(bool IsInterrupt, CRawCNBLList& toWaitingList)
{
case SUBMIT_NO_PLACE_IN_QUEUE:
NBLHolder->PushMappedNB(NBHolder);
PushMappedNBL(NBLHolder);
HaveBuffers = false;
// break the loop, allow to kick and free some buffers
break;
@@ -541,12 +577,12 @@ bool CParaNdisTX::SendMapped(bool IsInterrupt, CRawCNBLList& toWaitingList)
// if this NBL finished?
if (!NBLHolder->HaveMappedBuffers())
{
ASSERT(NBLHolder == PopMappedToSendNBL());
toWaitingList.Push(NBLHolder);
}
else
{
// no, insert it back to the queue
PushMappedNBL(NBLHolder);
// no, keep it in the queue
}
if (result == SUBMIT_SUCCESS)
@@ -584,51 +620,68 @@ bool CParaNdisTX::SendMapped(bool IsInterrupt, CRawCNBLList& toWaitingList)
return bRestartStatus;
}
bool CParaNdisTX::DoPendingTasks(CNBL *nblHolder)
bool CParaNdisTX::FillQueue()
{
BOOLEAN res = FALSE;
CNBL *nbl;
if (!m_SendQueueFullListIsEmpty)
{
TSpinLocker LockedContext(m_SendQueueFullListLock);
do
{
nbl = m_SendQueueFullList.Pop();
if (nbl != nullptr)
{
res = m_SendQueue.Enqueue(nbl);
if (!res)
{
m_SendQueueFullList.Push(nbl);
}
} else
{
break;
}
} while (res);
InterlockedExchange(&m_SendQueueFullListIsEmpty, m_SendQueueFullList.IsEmpty());
}
if (!m_SendQueueFullListIsEmpty && m_DpcWaiting == 0)
{
return TRUE;
}
return FALSE;
}
bool CParaNdisTX::DoPendingTasks()
{
PNET_BUFFER_LIST pNBLReturnNow = nullptr;
bool bRestartQueueStatus = false;
bool bFromDpc = nblHolder == nullptr;
CRawCNBList nbToFree;
CRawCNBLList completedNBLs;
if (bFromDpc)
{
m_DpcWaiting.AddRef();
}
m_DpcWaiting.AddRef();
DoWithTXLock([&] ()
{
if (nblHolder)
{
m_SendList.PushBack(nblHolder);
}
m_VirtQueue.ProcessTXCompletions(nbToFree);
m_DpcWaiting.Release();
if (bFromDpc)
{
m_DpcWaiting.Release();
}
if (bFromDpc || 0 == (LONG)m_DpcWaiting)
{
bRestartQueueStatus = SendMapped(bFromDpc, completedNBLs);
if (bRestartQueueStatus)
{
// we can enter here only when we called from DPC
// if we can't enable interrupts on queue right now,
// we can retrieve completed packets and try again
m_VirtQueue.ProcessTXCompletions(nbToFree);
bRestartQueueStatus = SendMapped(true, completedNBLs);
}
}
else
bRestartQueueStatus = SendMapped(TRUE, completedNBLs);
if (bRestartQueueStatus)
{
// the call initiated by Send(), we can give up
// and let pending DPC do the job instead of wait
// if we can't enable interrupts on queue right now,
// we can retrieve completed packets and try again
m_VirtQueue.ProcessTXCompletions(nbToFree);
bRestartQueueStatus = SendMapped(true, completedNBLs);
}
});
bRestartQueueStatus |= FillQueue();
if (!nbToFree.IsEmpty() || !completedNBLs.IsEmpty())
{
nbToFree.ForEachDetached([](CNB *NB)
+26 -5
View File
@@ -2,6 +2,10 @@
#include "ParaNdis-VirtQueue.h"
#include "ParaNdis-AbstractPath.h"
#include "ParaNdis_GuestAnnounce.h"
#include "ParaNdis_LockFreeQueue.h"
/* Must be a power of 2 */
#define PARANDIS_TX_LOCK_FREE_QUEUE_DEFAULT_SIZE 2048
class CNB;
class CParaNdisTX;
@@ -197,6 +201,8 @@ private:
typedef CNdisList<CNBL, CRawAccess, CNonCountingObject> CRawCNBLList;
typedef CLockFreeQueue<CNBL> CLockFreeCNBLQueue;
class CParaNdisTX : public CParaNdisTemplatePath<CTXVirtQueue>, public CNdisAllocatable<CParaNdisTX, 'XTHR'>
{
public:
@@ -229,7 +235,7 @@ public:
ULONG GetFreeHWBuffers()
{ return m_VirtQueue.GetFreeHWBuffers(); }
bool DoPendingTasks(CNBL *nblHolder = nullptr);
bool DoPendingTasks();
void CompleteOutstandingNBLChain(PNET_BUFFER_LIST NBL, ULONG Flags = 0);
void CompleteOutstandingInternalNBL(PNET_BUFFER_LIST NBL, BOOLEAN UnregisterOutstanding = TRUE);
@@ -237,12 +243,23 @@ private:
bool SendMapped(bool IsInterrupt, CRawCNBLList& toWaitingList);
bool FillQueue();
PNET_BUFFER_LIST ProcessWaitingList(CRawCNBLList& completed);
PNET_BUFFER_LIST BuildCancelList(PVOID CancelId);
bool HaveMappedNBLs() { return !m_SendList.IsEmpty(); }
CNBL *PopMappedNBL() { return m_SendList.Pop(); }
void PushMappedNBL(CNBL *NBLHolder) { m_SendList.Push(NBLHolder); }
bool HaveMappedNBLs() { return !m_SendQueue.IsEmpty(); }
CNBL *PopMappedNBL()
{
CNBL *nbl = PopMappedToSendNBL();
nbl = nbl == nullptr ? m_SendQueueFullList.Pop() : nbl;
return nbl;
}
CNBL *PopMappedToSendNBL() { return m_SendQueue.Dequeue(); }
CNBL *PeekMappedToSendNBL() { return m_SendQueue.Peek(); }
void PushMappedNBL(CNBL *NBLHolder) { m_SendQueue.Enqueue(NBLHolder); }
CDataFlowStateMachine m_StateMachine;
bool m_StateMachineRegistered = false;
@@ -250,7 +267,11 @@ private:
// indication that DPC waits on TX lock
CNdisRefCounter m_DpcWaiting;
CRawCNBLList m_SendList;
CLockFreeCNBLQueue m_SendQueue;
CRawCNBLList m_SendQueueFullList;
CNdisSpinLock m_SendQueueFullListLock;
volatile LONG m_SendQueueFullListIsEmpty;
CRawCNBLList m_WaitingList;
CNdisSpinLock m_WaitingListLock;