mirror of
https://github.com/arehnman/yttrium-virtio-gpu.git
synced 2026-11-07 02:06:32 +00:00
Current implementation of CParaNdisTX::BuildCancelList may trigger page fault and a bugcheck if m_SendQueue is already empty. Trap: 2: kd> .trap ffffcc81`1b5d2520 NOTE: The trap frame does not contain all registers. Some register values may be zeroed or incorrect. rax=0000000000000000 rbx=0000000000000000 rcx=00000000000019a4 rdx=00000000000019a5 rsi=0000000000000000 rdi=0000000000000000 rip=fffff80eae479edf rsp=ffffcc811b5d26b0 rbp=ffffa4873a248ee8 r8=ffffa487392bae80 r9=0000000000000002 r10=ffffa487391f0c30 r11=0000000000000001 r12=0000000000000000 r13=0000000000000000 r14=0000000000000000 r15=0000000000000000 iopl=0 nv up ei ng nz na po nc netkvm!CNBL::MatchCancelID [inlined in netkvm!CParaNdisTX::BuildCancelList+0x3f]: fffff80e`ae479edf 488b4320 mov rax,qword ptr [rbx+20h] ds:00000000`00000020=???????????????? 2: kd> r cr2 Last set context: cr2=0000000000000020 Callstack: 2: kd> kc *** Stack trace for last set context - .thread/.cxr resets it # Call Site 00 netkvm!CNBL::MatchCancelID 01 netkvm!CParaNdisTX::BuildCancelList 02 netkvm!CParaNdisTX::CancelNBLs 03 netkvm!ParaNdis6_CancelSendNetBufferLists 04 NDIS!ndisMCancelSendNetBufferListsOnMiniport 05 nt!KeExpandKernelStackAndCalloutInternal 06 NDIS!ndisExpandStack 07 NDIS!ndisFilterCancelSendNetBufferLists 08 klim6 09 NDIS!ndisFInvokePause 0a NDIS!ndisPauseFilterInner 0b NDIS!ndisPauseFilter 0c NDIS!Ndis::BindEngine::Iterate 0d NDIS!Ndis::BindEngine::UpdateBindings 0e NDIS!Ndis::BindEngine::DispatchPendingWork 0f NDIS!Ndis::BindEngine::ApplyBindChanges 10 NDIS!ndisPnPRemoveDevice 11 NDIS!ndisPnPRemoveDeviceEx 12 NDIS!ndisPnPIrpRemoveDevice 13 NDIS!ndisPnPDispatch 14 nt!IopSynchronousCall 15 nt!IopRemoveDevice 16 nt!PnpRemoveLockedDeviceNode 17 nt!PnpDeleteLockedDeviceNode 18 nt!PnpDeleteLockedDeviceNodes 19 nt!PnpProcessQueryRemoveAndEject 1a nt!PnpProcessTargetDeviceEvent 1b nt!PnpDeviceEventWorker 1c nt!ExpWorkerThread 1d nt!PspSystemThreadStartup 1e nt!KiStartSystemThread Queue state: 2: kd> dt netkvm!CLockFreeDynamicQueue<CNBL> 0xffffa4873a248ee8 +0x000 m_Queue : CLockFreeQueue<CNBL> +0x030 m_QueueFullList : CNdisList<CNBL,CRawAccess,CNonCountingObject> +0x048 m_QueueFullListLock : CNdisSpinLock +0x058 m_QueueFullListIsEmpty : 0n1 +0x05c m_ElementCount : 0n0 +0x060 m_Size : 0n8192 The fix is trivial: check if CNBL is empty before trying to access its fields. Signed-off-by: Sergey Bykov <[email protected]>
1061 lines
30 KiB
C++
1061 lines
30 KiB
C++
#include "ndis56common.h"
|
|
#include "kdebugprint.h"
|
|
#include "ParaNdis_DebugHistory.h"
|
|
#include "Trace.h"
|
|
#ifdef NETKVM_WPP_ENABLED
|
|
#include "ParaNdis_TX.tmh"
|
|
#endif
|
|
|
|
CNBL::CNBL(PNET_BUFFER_LIST NBL, PPARANDIS_ADAPTER Context, CParaNdisTX &ParentTXPath, CAllocationHelper<CNBL> *NBLAllocator, CAllocationHelper<CNB> *NBAllocator)
|
|
: m_NBL(NBL)
|
|
, m_Context(Context)
|
|
, m_ParentTXPath(&ParentTXPath)
|
|
, CNdisAllocatableViaHelper<CNBL>(NBLAllocator)
|
|
, m_NBAllocator(NBAllocator)
|
|
{
|
|
m_NBL->Scratch = this;
|
|
m_LsoInfo.Value = NET_BUFFER_LIST_INFO(m_NBL, TcpLargeSendNetBufferListInfo);
|
|
m_CsoInfo.Value = NET_BUFFER_LIST_INFO(m_NBL, TcpIpChecksumNetBufferListInfo);
|
|
#if PARANDIS_SUPPORT_USO
|
|
m_UsoInfo.Value = NET_BUFFER_LIST_INFO(m_NBL, UdpSegmentationOffloadInfo);
|
|
#endif
|
|
ParaNdis_DebugNBLIn(NBL, m_LogIndex);
|
|
}
|
|
|
|
CNBL::~CNBL()
|
|
{
|
|
CDpcIrqlRaiser OnDpc;
|
|
|
|
m_Buffers.ForEachDetached([this](CNB *NB)
|
|
{ CNB::Destroy(NB); });
|
|
|
|
if(m_NBL)
|
|
{
|
|
auto NBL = DetachInternalObject();
|
|
NETKVM_ASSERT(NET_BUFFER_LIST_NEXT_NBL(NBL) == nullptr);
|
|
if (CallCompletionForNBL(m_Context, NBL))
|
|
{
|
|
m_ParentTXPath->CompleteOutstandingNBLChain(NBL);
|
|
} else
|
|
{
|
|
m_ParentTXPath->CompleteOutstandingInternalNBL(NBL);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CNBL::ParsePriority()
|
|
{
|
|
NDIS_NET_BUFFER_LIST_8021Q_INFO priorityInfo;
|
|
priorityInfo.Value = m_Context->ulPriorityVlanSetting ?
|
|
NET_BUFFER_LIST_INFO(m_NBL, Ieee8021QNetBufferListInfo) : nullptr;
|
|
|
|
if (!priorityInfo.TagHeader.VlanId)
|
|
{
|
|
priorityInfo.TagHeader.VlanId = m_Context->VlanId;
|
|
}
|
|
|
|
if (priorityInfo.TagHeader.CanonicalFormatId || !IsValidVlanId(m_Context, priorityInfo.TagHeader.VlanId))
|
|
{
|
|
DPrintf(0, "[%s] Discarded invalid priority tag %p\n", __FUNCTION__, priorityInfo.Value);
|
|
return false;
|
|
}
|
|
else if (priorityInfo.Value)
|
|
{
|
|
// ignore priority, if configured
|
|
if (!IsPrioritySupported(m_Context))
|
|
priorityInfo.TagHeader.UserPriority = 0;
|
|
// ignore VlanId, if specified
|
|
if (!IsVlanSupported(m_Context))
|
|
priorityInfo.TagHeader.VlanId = 0;
|
|
if (priorityInfo.Value)
|
|
{
|
|
m_TCI = static_cast<UINT16>(priorityInfo.TagHeader.UserPriority << 13 | priorityInfo.TagHeader.VlanId);
|
|
DPrintf(1, "[%s] Populated priority tag %p\n", __FUNCTION__, priorityInfo.Value);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CNBL::RegisterNB(CNB *NB)
|
|
{
|
|
m_Buffers.PushBack(NB);
|
|
m_BuffersNumber++;
|
|
}
|
|
|
|
void CNBL::RegisterMappedNB(CNB *NB)
|
|
{
|
|
UNREFERENCED_PARAMETER(NB);
|
|
if (m_BuffersNumber == (ULONG)m_BuffersMapped.AddRef())
|
|
{
|
|
m_ParentTXPath->NBLMappingDone(this);
|
|
}
|
|
}
|
|
|
|
bool CNBL::ParseBuffers()
|
|
{
|
|
m_MaxDataLength = 0;
|
|
CAllocationHelper<CNB> *pNBAllocator = this;
|
|
|
|
for (auto NB = NET_BUFFER_LIST_FIRST_NB(m_NBL); NB != nullptr; NB = NET_BUFFER_NEXT_NB(NB))
|
|
{
|
|
CNB *NBHolder = new (pNBAllocator) CNB(NB, this, m_Context, pNBAllocator);
|
|
pNBAllocator = m_NBAllocator;
|
|
if(!NBHolder || !NBHolder->IsValid())
|
|
{
|
|
return false;
|
|
}
|
|
RegisterNB(NBHolder);
|
|
m_MaxDataLength = max(m_MaxDataLength, NBHolder->GetDataLength());
|
|
}
|
|
|
|
if(m_MaxDataLength == 0)
|
|
{
|
|
DPrintf(0, "[%s] - Empty NBL (%p) dropped\n", __FUNCTION__, m_NBL);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CNBL::NeedsLSO()
|
|
{
|
|
return m_MaxDataLength > m_Context->MaxPacketSize.nMaxFullSizeOS;
|
|
}
|
|
|
|
bool CNBL::FitsLSO()
|
|
{
|
|
return (m_MaxDataLength <= PARANDIS_MAX_LSO_SIZE + LsoTcpHeaderOffset() + MAX_TCP_HEADER_SIZE);
|
|
}
|
|
|
|
bool CNBL::ParseLSO()
|
|
{
|
|
NETKVM_ASSERT(IsLSO());
|
|
|
|
if (m_LsoInfo.LsoV1Transmit.Type != NDIS_TCP_LARGE_SEND_OFFLOAD_V1_TYPE &&
|
|
m_LsoInfo.LsoV2Transmit.Type != NDIS_TCP_LARGE_SEND_OFFLOAD_V2_TYPE)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (NeedsLSO() &&
|
|
(!m_LsoInfo.LsoV2Transmit.MSS ||
|
|
!m_LsoInfo.LsoV2Transmit.TcpHeaderOffset))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!FitsLSO())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!LsoTcpHeaderOffset() != !MSS())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ((!m_Context->Offload.flags.fTxLso || !m_Context->bOffloadv4Enabled) &&
|
|
m_LsoInfo.LsoV2Transmit.IPVersion == NDIS_TCP_LARGE_SEND_OFFLOAD_IPv4)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (m_LsoInfo.LsoV2Transmit.Type == NDIS_TCP_LARGE_SEND_OFFLOAD_V2_TYPE &&
|
|
m_LsoInfo.LsoV2Transmit.IPVersion == NDIS_TCP_LARGE_SEND_OFFLOAD_IPv6 &&
|
|
(!m_Context->Offload.flags.fTxLsov6 || !m_Context->bOffloadv6Enabled))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#if PARANDIS_SUPPORT_USO
|
|
bool CNBL::ParseUSO()
|
|
{
|
|
if (m_MaxDataLength > PARANDIS_MAX_LSO_SIZE + LsoTcpHeaderOffset() + sizeof(UDPHeader))
|
|
return false;
|
|
if (!m_UsoInfo.Transmit.MSS || !m_UsoInfo.Transmit.UdpHeaderOffset)
|
|
return false;
|
|
if (m_UsoInfo.Transmit.IPVersion == NDIS_UDP_SEGMENTATION_OFFLOAD_IPV4 && !m_Context->Offload.flags.fUsov4)
|
|
return false;
|
|
if (m_UsoInfo.Transmit.IPVersion == NDIS_UDP_SEGMENTATION_OFFLOAD_IPV6 && !m_Context->Offload.flags.fUsov6)
|
|
return false;
|
|
return true;
|
|
}
|
|
#else
|
|
bool CNBL::ParseUSO()
|
|
{
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
template <typename TClassPred, typename TOffloadPred, typename TSupportedPred>
|
|
bool CNBL::ParseCSO(TClassPred IsClass, TOffloadPred IsOffload,
|
|
TSupportedPred IsSupported, LPSTR OffloadName)
|
|
{
|
|
NETKVM_ASSERT(IsClass());
|
|
UNREFERENCED_PARAMETER(IsClass);
|
|
|
|
if (IsOffload())
|
|
{
|
|
if(!IsSupported())
|
|
{
|
|
DPrintf(0, "[%s] %s request when it is not supported\n", __FUNCTION__, OffloadName);
|
|
#if FAIL_UNEXPECTED
|
|
// ignore unexpected CS requests while this passes WHQL
|
|
return false;
|
|
#endif
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CNBL::ParseOffloads()
|
|
{
|
|
if (IsLSO())
|
|
{
|
|
if(!ParseLSO())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else if (IsUSO())
|
|
{
|
|
if (!ParseUSO())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else if (IsIP4CSO())
|
|
{
|
|
if(!ParseCSO([this] () -> bool { return IsIP4CSO(); },
|
|
[this] () -> bool { return m_CsoInfo.Transmit.TcpChecksum; },
|
|
[this] () -> bool { return m_Context->Offload.flags.fTxTCPChecksum && m_Context->bOffloadv4Enabled; },
|
|
"TCP4 CSO"))
|
|
{
|
|
return false;
|
|
}
|
|
else if(!ParseCSO([this] () -> bool { return IsIP4CSO(); },
|
|
[this] () -> bool { return m_CsoInfo.Transmit.UdpChecksum; },
|
|
[this] () -> bool { return m_Context->Offload.flags.fTxUDPChecksum && m_Context->bOffloadv4Enabled; },
|
|
"UDP4 CSO"))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if(!ParseCSO([this] () -> bool { return IsIP4CSO(); },
|
|
[this] () -> bool { return m_CsoInfo.Transmit.IpHeaderChecksum; },
|
|
[this] () -> bool { return m_Context->Offload.flags.fTxIPChecksum && m_Context->bOffloadv4Enabled; },
|
|
"IP4 CSO"))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else if (IsIP6CSO())
|
|
{
|
|
if(!ParseCSO([this] () -> bool { return IsIP6CSO(); },
|
|
[this] () -> bool { return m_CsoInfo.Transmit.TcpChecksum; },
|
|
[this] () -> bool { return m_Context->Offload.flags.fTxTCPv6Checksum && m_Context->bOffloadv6Enabled; },
|
|
"TCP6 CSO"))
|
|
{
|
|
return false;
|
|
}
|
|
else if(!ParseCSO([this] () -> bool { return IsIP6CSO(); },
|
|
[this] () -> bool { return m_CsoInfo.Transmit.UdpChecksum; },
|
|
[this] () -> bool { return m_Context->Offload.flags.fTxUDPv6Checksum && m_Context->bOffloadv6Enabled; },
|
|
"UDP6 CSO"))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CNBL::StartMapping()
|
|
{
|
|
CDpcIrqlRaiser OnDpc;
|
|
|
|
AddRef();
|
|
|
|
m_Buffers.ForEach([this](CNB *NB)
|
|
{
|
|
if (!NB->ScheduleBuildSGListForTx())
|
|
{
|
|
m_HaveFailedMappings = true;
|
|
NB->MappingDone(nullptr);
|
|
}
|
|
});
|
|
|
|
Release();
|
|
}
|
|
|
|
void CNBL::OnLastReferenceGone()
|
|
{
|
|
Destroy(this);
|
|
}
|
|
|
|
CParaNdisTX::~CParaNdisTX()
|
|
{
|
|
|
|
TPassiveSpinLocker LockedContext(m_Lock);
|
|
CNBL* NBL = nullptr;
|
|
|
|
NBL = m_SendQueue.Dequeue();
|
|
|
|
while (NBL)
|
|
{
|
|
NBL->~CNBL();
|
|
NBL = m_SendQueue.Dequeue();
|
|
}
|
|
|
|
DPrintf(1, "Pools state %d-> NB: %d, NBL: %d\n", m_queueIndex, m_nbPool.GetCount(), m_nblPool.GetCount());
|
|
if (m_StateMachineRegistered)
|
|
{
|
|
m_Context->m_StateMachine.UnregisterFlow(m_StateMachine);
|
|
m_StateMachineRegistered = false;
|
|
}
|
|
}
|
|
|
|
bool CParaNdisTX::Create(PPARANDIS_ADAPTER Context, UINT DeviceQueueIndex)
|
|
{
|
|
m_Context = Context;
|
|
m_queueIndex = (u16)DeviceQueueIndex;
|
|
|
|
Context->m_StateMachine.RegisterFlow(m_StateMachine);
|
|
m_StateMachineRegistered = true;
|
|
|
|
m_nbPool.Create(Context->MiniportHandle);
|
|
m_nblPool.Create(Context->MiniportHandle);
|
|
|
|
CreatePath();
|
|
|
|
return m_VirtQueue.Create(DeviceQueueIndex,
|
|
&m_Context->IODevice,
|
|
m_Context->MiniportHandle,
|
|
m_Context->maxFreeTxDescriptors,
|
|
m_Context->nVirtioHeaderSize,
|
|
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)
|
|
{
|
|
ULONG NBLNum = ParaNdis_CountNBLs(NBL);
|
|
|
|
ParaNdis_CompleteNBLChain(m_Context->MiniportHandle, NBL, Flags);
|
|
|
|
m_StateMachine.UnregisterOutstandingItems(NBLNum);
|
|
}
|
|
|
|
void CParaNdisTX::CompleteOutstandingInternalNBL(PNET_BUFFER_LIST NBL, BOOLEAN UnregisterOutstanding /*= TRUE*/)
|
|
{
|
|
ULONG NBLNum = ParaNdis_CountNBLs(NBL);
|
|
|
|
CGuestAnnouncePackets::NblCompletionCallback(NBL);
|
|
|
|
if (UnregisterOutstanding)
|
|
{
|
|
m_StateMachine.UnregisterOutstandingItems(NBLNum);
|
|
}
|
|
}
|
|
|
|
void CParaNdisTX::Send(PNET_BUFFER_LIST NBL)
|
|
{
|
|
PNET_BUFFER_LIST nextNBL = nullptr;
|
|
NDIS_STATUS RejectionStatus = NDIS_STATUS_FAILURE;
|
|
BOOLEAN CallCompletion = CallCompletionForNBL(m_Context, NBL);
|
|
|
|
if (!m_StateMachine.RegisterOutstandingItems(ParaNdis_CountNBLs(NBL), &RejectionStatus))
|
|
{
|
|
if (CallCompletion)
|
|
{
|
|
ParaNdis_CompleteNBLChainWithStatus(m_Context->MiniportHandle, NBL, RejectionStatus);
|
|
}
|
|
else
|
|
{
|
|
CompleteOutstandingInternalNBL(NBL, FALSE);
|
|
}
|
|
return;
|
|
}
|
|
|
|
for(auto currNBL = NBL; currNBL != nullptr; currNBL = nextNBL)
|
|
{
|
|
nextNBL = NET_BUFFER_LIST_NEXT_NBL(currNBL);
|
|
NET_BUFFER_LIST_NEXT_NBL(currNBL) = nullptr;
|
|
|
|
auto NBLHolder = new (&m_nblPool) CNBL(currNBL, m_Context, *this, &m_nblPool, &m_nbPool);
|
|
|
|
if (NBLHolder == nullptr)
|
|
{
|
|
currNBL->Status = NDIS_STATUS_RESOURCES;
|
|
if (CallCompletion)
|
|
{
|
|
CompleteOutstandingNBLChain(currNBL);
|
|
}
|
|
else
|
|
{
|
|
CompleteOutstandingInternalNBL(NBL);
|
|
}
|
|
DPrintf(0, "ERROR: Failed to allocate CNBL instance\n");
|
|
continue;
|
|
}
|
|
|
|
if(NBLHolder->Prepare() &&
|
|
ParaNdis_IsTxRxPossible(m_Context))
|
|
{
|
|
NBLHolder->StartMapping();
|
|
}
|
|
else
|
|
{
|
|
NBLHolder->SetStatus(ParaNdis_ExactSendFailureStatus(m_Context));
|
|
NBLHolder->Release();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CParaNdisTX::NBLMappingDone(CNBL *NBLHolder)
|
|
{
|
|
NETKVM_ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
|
|
|
|
if (NBLHolder->MappingSucceeded() && m_VirtQueue.Alive())
|
|
{
|
|
m_SendQueue.Enqueue(NBLHolder);
|
|
|
|
if (m_DpcWaiting == 0)
|
|
{
|
|
DoPendingTasks(NBLHolder);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NBLHolder->SetStatus(NDIS_STATUS_FAILURE);
|
|
NBLHolder->Release();
|
|
}
|
|
}
|
|
|
|
CNB *CNBL::PopMappedNB()
|
|
{
|
|
m_MappedBuffersDetached.AddRef();
|
|
return m_Buffers.Pop();
|
|
}
|
|
void CNBL::PushMappedNB(CNB *NB)
|
|
{
|
|
m_MappedBuffersDetached.Release();
|
|
m_Buffers.Push(NB);
|
|
}
|
|
|
|
void CNBL::NBComplete()
|
|
{
|
|
m_BuffersDone.AddRef();
|
|
m_MappedBuffersDetached.Release();
|
|
}
|
|
|
|
bool CNBL::IsSendDone()
|
|
{
|
|
return (LONG)m_BuffersDone == (LONG)m_BuffersNumber && !HaveDetachedBuffers();
|
|
}
|
|
|
|
PNET_BUFFER_LIST CNBL::DetachInternalObject()
|
|
{
|
|
m_Buffers.ForEach([this](CNB *NB)
|
|
{
|
|
NB->ReleaseResources();
|
|
});
|
|
|
|
// do it for both LsoV1 and LsoV2
|
|
if (IsLSO())
|
|
{
|
|
m_LsoInfo.LsoV1TransmitComplete.TcpPayload = m_TransferSize;
|
|
}
|
|
|
|
//Flush changes made in LSO structures
|
|
NET_BUFFER_LIST_INFO(m_NBL, TcpLargeSendNetBufferListInfo) = m_LsoInfo.Value;
|
|
|
|
auto Res = m_NBL;
|
|
ParaNdis_DebugNBLOut(m_LogIndex, Res);
|
|
m_NBL = nullptr;
|
|
return Res;
|
|
}
|
|
|
|
PNET_BUFFER_LIST CParaNdisTX::ProcessWaitingList(CRawCNBLList& completed)
|
|
{
|
|
PNET_BUFFER_LIST CompletedNBLs = nullptr;
|
|
|
|
// locked part under waiting list lock
|
|
{
|
|
TDPCSpinLocker LockedContext(m_WaitingListLock);
|
|
|
|
completed.ForEachDetachedIf([](CNBL* NBL) { return !NBL->IsSendDone(); },
|
|
[&](CNBL* NBL)
|
|
{
|
|
m_WaitingList.PushBack(NBL);
|
|
});
|
|
|
|
m_WaitingList.ForEachDetachedIf([](CNBL* NBL) { return NBL->IsSendDone(); },
|
|
[&](CNBL* NBL)
|
|
{
|
|
completed.PushBack(NBL);
|
|
});
|
|
}
|
|
// end of locked part under waiting list lock
|
|
|
|
completed.ForEachDetached([&](CNBL* NBL)
|
|
{
|
|
NBL->SetStatus(NDIS_STATUS_SUCCESS);
|
|
auto RawNBL = NBL->DetachInternalObject();
|
|
NBL->Release();
|
|
if (CallCompletionForNBL(m_Context, RawNBL))
|
|
{
|
|
NET_BUFFER_LIST_NEXT_NBL(RawNBL) = CompletedNBLs;
|
|
CompletedNBLs = RawNBL;
|
|
}
|
|
else
|
|
{
|
|
CompleteOutstandingInternalNBL(RawNBL);
|
|
}
|
|
});
|
|
|
|
return CompletedNBLs;
|
|
}
|
|
|
|
void CParaNdisTX::Notify(SMNotifications message)
|
|
{
|
|
CRawCNBList nbToFree;
|
|
CRawCNBLList completedNBLs;
|
|
|
|
__super::Notify(message);
|
|
|
|
if (message != SupriseRemoved)
|
|
{
|
|
// probably similar processing we'll do in case of reset
|
|
return;
|
|
}
|
|
|
|
CDpcIrqlRaiser OnDpc;
|
|
|
|
// pause follows after we return all the NBLs
|
|
DoWithTXLock([&]()
|
|
{
|
|
//
|
|
m_VirtQueue.ProcessTXCompletions(nbToFree, true);
|
|
|
|
while (HaveMappedNBLs())
|
|
{
|
|
CNBL *nbl = PopMappedNBL();
|
|
nbl->SetStatus(NDIS_STATUS_SEND_ABORTED);
|
|
completedNBLs.Push(nbl);
|
|
while (nbl->HaveMappedBuffers())
|
|
{
|
|
CNB *nb = nbl->PopMappedNB();
|
|
CNB::Destroy(nb);
|
|
nbl->NBComplete();
|
|
}
|
|
}
|
|
});
|
|
|
|
PostProcessPendingTask(nbToFree, completedNBLs);
|
|
}
|
|
|
|
PNET_BUFFER_LIST CParaNdisTX::BuildCancelList(PVOID CancelId)
|
|
{
|
|
PNET_BUFFER_LIST CanceledNBLs = nullptr;
|
|
TPassiveSpinLocker LockedContext(m_Lock);
|
|
CNBL* NBL = nullptr;
|
|
|
|
NBL = PopMappedNBL();
|
|
while (NBL)
|
|
{
|
|
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();
|
|
}
|
|
|
|
return CanceledNBLs;
|
|
}
|
|
|
|
void CParaNdisTX::CancelNBLs(PVOID CancelId)
|
|
{
|
|
auto CanceledNBLs = BuildCancelList(CancelId);
|
|
if (CanceledNBLs != nullptr)
|
|
{
|
|
CompleteOutstandingNBLChain(CanceledNBLs);
|
|
}
|
|
}
|
|
|
|
//called with TX lock held
|
|
bool CParaNdisTX::RestartQueue()
|
|
{
|
|
auto res = ParaNdis_SynchronizeWithInterrupt(m_Context,
|
|
m_messageIndex,
|
|
RestartQueueSynchronously,
|
|
this) ? true : false;
|
|
return res;
|
|
}
|
|
|
|
//called with TX lock held
|
|
//returns queue restart status
|
|
bool CParaNdisTX::SendMapped(bool IsInterrupt, CRawCNBLList& toWaitingList)
|
|
{
|
|
bool SentOutSomeBuffers = false;
|
|
bool bRestartStatus = false;
|
|
bool HaveBuffers = true;
|
|
|
|
if(ParaNdis_IsTxRxPossible(m_Context))
|
|
{
|
|
|
|
while (HaveBuffers && HaveMappedNBLs())
|
|
{
|
|
auto NBLHolder = PeekMappedNBL();
|
|
|
|
if (NBLHolder->HaveMappedBuffers())
|
|
{
|
|
auto NBHolder = NBLHolder->PopMappedNB();
|
|
auto result = m_VirtQueue.SubmitPacket(*NBHolder);
|
|
|
|
switch (result)
|
|
{
|
|
case SUBMIT_NO_PLACE_IN_QUEUE:
|
|
NBLHolder->PushMappedNB(NBHolder);
|
|
HaveBuffers = false;
|
|
// break the loop, allow to kick and free some buffers
|
|
break;
|
|
|
|
case SUBMIT_FAILURE:
|
|
__fallthrough;
|
|
case SUBMIT_SUCCESS:
|
|
__fallthrough;
|
|
case SUBMIT_PACKET_TOO_LARGE:
|
|
// if this NBL finished?
|
|
if (!NBLHolder->HaveMappedBuffers())
|
|
{
|
|
/* We use PeekMappedNBL method to get the current NBL
|
|
* that should be processed from the queue, when we finish
|
|
* sending all it's NBs, we should pop it from the queue.
|
|
*/
|
|
PopMappedNBL();
|
|
toWaitingList.Push(NBLHolder);
|
|
}
|
|
else
|
|
{
|
|
// no, keep it in the queue
|
|
}
|
|
|
|
if (result == SUBMIT_SUCCESS)
|
|
{
|
|
SentOutSomeBuffers = true;
|
|
}
|
|
else
|
|
{
|
|
CNB::Destroy(NBHolder);
|
|
NBLHolder->NBComplete();
|
|
}
|
|
break;
|
|
default:
|
|
NETKVM_ASSERT(false);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NETKVM_ASSERT(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (IsInterrupt)
|
|
{
|
|
bRestartStatus = RestartQueue();
|
|
}
|
|
|
|
if (SentOutSomeBuffers || !HaveBuffers)
|
|
{
|
|
m_VirtQueue.Kick();
|
|
}
|
|
|
|
return bRestartStatus;
|
|
}
|
|
|
|
void CParaNdisTX::PostProcessPendingTask(
|
|
CRawCNBList& nbToFree,
|
|
CRawCNBLList& completedNBLs)
|
|
{
|
|
PNET_BUFFER_LIST pNBLReturnNow = nullptr;
|
|
if (!nbToFree.IsEmpty() || !completedNBLs.IsEmpty())
|
|
{
|
|
nbToFree.ForEachDetached([](CNB *NB)
|
|
{
|
|
CNBL *NBL = NB->GetParentNBL();
|
|
CNB::Destroy(NB);
|
|
NBL->NBComplete();
|
|
}
|
|
);
|
|
|
|
pNBLReturnNow = ProcessWaitingList(completedNBLs);
|
|
if (pNBLReturnNow)
|
|
{
|
|
CompleteOutstandingNBLChain(pNBLReturnNow, NDIS_SEND_COMPLETE_FLAGS_DISPATCH_LEVEL);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CParaNdisTX::DoPendingTasks(CNBL *nblHolder)
|
|
{
|
|
bool bRestartQueueStatus = false;
|
|
bool bFromDpc = nblHolder == nullptr;
|
|
CRawCNBList nbToFree;
|
|
CRawCNBLList completedNBLs;
|
|
|
|
if (bFromDpc)
|
|
{
|
|
m_DpcWaiting.AddRef();
|
|
}
|
|
|
|
DoWithTXLock([&]()
|
|
{
|
|
m_VirtQueue.ProcessTXCompletions(nbToFree);
|
|
|
|
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
|
|
{
|
|
// the call initiated by Send(), we can give up
|
|
// and let pending DPC do the job instead of wait
|
|
}
|
|
});
|
|
|
|
PostProcessPendingTask(nbToFree, completedNBLs);
|
|
|
|
return bRestartQueueStatus;
|
|
}
|
|
|
|
void CNB::MappingDone(PSCATTER_GATHER_LIST SGL)
|
|
{
|
|
m_SGL = SGL;
|
|
m_ParentNBL->RegisterMappedNB(this);
|
|
}
|
|
|
|
CNB::~CNB()
|
|
{
|
|
NETKVM_ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
|
|
|
|
if(m_SGL != nullptr)
|
|
{
|
|
NdisMFreeNetBufferSGList(m_Context->DmaHandle, m_SGL, m_NB);
|
|
}
|
|
}
|
|
|
|
void CNB::ReleaseResources()
|
|
{
|
|
if (m_SGL != nullptr)
|
|
{
|
|
NdisMFreeNetBufferSGList(m_Context->DmaHandle, m_SGL, m_NB);
|
|
m_SGL = nullptr;
|
|
}
|
|
}
|
|
|
|
bool CNB::ScheduleBuildSGListForTx()
|
|
{
|
|
NETKVM_ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
|
|
|
|
return NdisMAllocateNetBufferSGList(m_Context->DmaHandle, m_NB, this,
|
|
NDIS_SG_LIST_WRITE_TO_DEVICE, nullptr, 0) == NDIS_STATUS_SUCCESS;
|
|
}
|
|
|
|
void CNB::PopulateIPLength(IPHeader *IpHeader, USHORT IpLength) const
|
|
{
|
|
if ((IpHeader->v4.ip_verlen & 0xF0) == 0x40)
|
|
{
|
|
if (!IpHeader->v4.ip_length)
|
|
{
|
|
IpHeader->v4.ip_length = swap_short(IpLength);
|
|
}
|
|
}
|
|
else if ((IpHeader->v6.ip6_ver_tc & 0xF0) == 0x60)
|
|
{
|
|
if (!IpHeader->v6.ip6_payload_len)
|
|
{
|
|
IpHeader->v6.ip6_payload_len = swap_short(IpLength - IPV6_HEADER_MIN_SIZE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DPrintf(0, "[%s] ERROR: Bad version of IP header!\n", __FUNCTION__);
|
|
}
|
|
}
|
|
|
|
void CNB::SetupLSO(virtio_net_hdr *VirtioHeader, PVOID IpHeader, ULONG EthPayloadLength) const
|
|
{
|
|
PopulateIPLength(reinterpret_cast<IPHeader*>(IpHeader), static_cast<USHORT>(EthPayloadLength));
|
|
|
|
tTcpIpPacketParsingResult packetReview;
|
|
packetReview = ParaNdis_CheckSumVerifyFlat(reinterpret_cast<IPv4Header*>(IpHeader), EthPayloadLength,
|
|
pcrIpChecksum | pcrFixIPChecksum | pcrTcpChecksum | pcrFixPHChecksum,
|
|
FALSE,
|
|
__FUNCTION__);
|
|
|
|
if (packetReview.xxpCheckSum == ppresPCSOK || packetReview.fixedXxpCS)
|
|
{
|
|
auto IpHeaderOffset = m_Context->Offload.ipHeaderOffset;
|
|
auto VHeader = static_cast<virtio_net_hdr*>(VirtioHeader);
|
|
auto PriorityHdrLen = (m_ParentNBL->TCI() != 0) ? ETH_PRIORITY_HEADER_SIZE : 0;
|
|
|
|
VHeader->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM;
|
|
VHeader->gso_type = packetReview.ipStatus == ppresIPV4 ? VIRTIO_NET_HDR_GSO_TCPV4 : VIRTIO_NET_HDR_GSO_TCPV6;
|
|
VHeader->hdr_len = (USHORT)(packetReview.XxpIpHeaderSize + IpHeaderOffset + PriorityHdrLen);
|
|
VHeader->gso_size = (USHORT)m_ParentNBL->MSS();
|
|
VHeader->csum_start = (USHORT)(m_ParentNBL->TCPHeaderOffset() + PriorityHdrLen);
|
|
VHeader->csum_offset = TCP_CHECKSUM_OFFSET;
|
|
}
|
|
}
|
|
|
|
void CNB::SetupUSO(virtio_net_hdr *VirtioHeader, PVOID IpHeader, ULONG EthPayloadLength) const
|
|
{
|
|
PopulateIPLength(reinterpret_cast<IPHeader*>(IpHeader), static_cast<USHORT>(EthPayloadLength));
|
|
|
|
tTcpIpPacketParsingResult packetReview;
|
|
packetReview = ParaNdis_CheckSumVerifyFlat(reinterpret_cast<IPv4Header*>(IpHeader), EthPayloadLength,
|
|
pcrIpChecksum | pcrFixIPChecksum | pcrUdpChecksum | pcrFixPHChecksum,
|
|
FALSE,
|
|
__FUNCTION__);
|
|
|
|
if (packetReview.xxpCheckSum == ppresPCSOK || packetReview.fixedXxpCS)
|
|
{
|
|
auto IpHeaderOffset = m_Context->Offload.ipHeaderOffset;
|
|
auto VHeader = static_cast<virtio_net_hdr*>(VirtioHeader);
|
|
auto PriorityHdrLen = (m_ParentNBL->TCI() != 0) ? ETH_PRIORITY_HEADER_SIZE : 0;
|
|
|
|
VHeader->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM;
|
|
VHeader->gso_type = VIRTIO_NET_HDR_GSO_UDP_L4;
|
|
VHeader->hdr_len = (USHORT)(packetReview.XxpIpHeaderSize + IpHeaderOffset + PriorityHdrLen);
|
|
VHeader->gso_size = (USHORT)m_ParentNBL->UsoMSS();
|
|
VHeader->csum_start = (USHORT)(m_ParentNBL->UsoHeaderOffset() + PriorityHdrLen);
|
|
VHeader->csum_offset = UDP_CHECKSUM_OFFSET;
|
|
DPrintf(0, "[%s] mss %d, hdr %d, total %d!\n", __FUNCTION__, VHeader->gso_size, VHeader->hdr_len, GetDataLength());
|
|
}
|
|
}
|
|
|
|
USHORT CNB::QueryL4HeaderOffset(PVOID PacketData, ULONG IpHeaderOffset) const
|
|
{
|
|
USHORT Res;
|
|
auto ppr = ParaNdis_ReviewIPPacket(RtlOffsetToPointer(PacketData, IpHeaderOffset),
|
|
GetDataLength(), FALSE, __FUNCTION__);
|
|
if (ppr.ipStatus != ppresNotIP)
|
|
{
|
|
Res = static_cast<USHORT>(IpHeaderOffset + ppr.ipHeaderSize);
|
|
}
|
|
else
|
|
{
|
|
DPrintf(0, "[%s] ERROR: NOT an IP packet - expected troubles!\n", __FUNCTION__);
|
|
Res = 0;
|
|
}
|
|
return Res;
|
|
}
|
|
|
|
void CNB::SetupCSO(virtio_net_hdr *VirtioHeader, ULONG L4HeaderOffset) const
|
|
{
|
|
u16 PriorityHdrLen = m_ParentNBL->TCI() ? ETH_PRIORITY_HEADER_SIZE : 0;
|
|
|
|
VirtioHeader->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM;
|
|
VirtioHeader->csum_start = static_cast<u16>(L4HeaderOffset) + PriorityHdrLen;
|
|
VirtioHeader->csum_offset = m_ParentNBL->IsTcpCSO() ? TCP_CHECKSUM_OFFSET : UDP_CHECKSUM_OFFSET;
|
|
}
|
|
|
|
void CNB::DoIPHdrCSO(PVOID IpHeader, ULONG EthPayloadLength) const
|
|
{
|
|
ParaNdis_CheckSumVerifyFlat(IpHeader,
|
|
EthPayloadLength,
|
|
pcrIpChecksum | pcrFixIPChecksum, FALSE,
|
|
__FUNCTION__);
|
|
}
|
|
|
|
bool CNB::FillDescriptorSGList(CTXDescriptor &Descriptor, ULONG ParsedHeadersLength) const
|
|
{
|
|
return Descriptor.SetupHeaders(ParsedHeadersLength) &&
|
|
MapDataToVirtioSGL(Descriptor, ParsedHeadersLength + NET_BUFFER_DATA_OFFSET(m_NB));
|
|
}
|
|
|
|
bool CNB::MapDataToVirtioSGL(CTXDescriptor &Descriptor, ULONG Offset) const
|
|
{
|
|
for (ULONG i = 0; i < m_SGL->NumberOfElements; i++)
|
|
{
|
|
if (Offset < m_SGL->Elements[i].Length)
|
|
{
|
|
PHYSICAL_ADDRESS PA;
|
|
PA.QuadPart = m_SGL->Elements[i].Address.QuadPart + Offset;
|
|
|
|
if (!Descriptor.AddDataChunk(PA, m_SGL->Elements[i].Length - Offset))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Offset = 0;
|
|
}
|
|
else
|
|
{
|
|
Offset -= m_SGL->Elements[i].Length;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CNB::CopyHeaders(PVOID Destination, ULONG MaxSize, ULONG &HeadersLength, ULONG &L4HeaderOffset) const
|
|
{
|
|
HeadersLength = 0;
|
|
L4HeaderOffset = 0;
|
|
|
|
if (m_ParentNBL->IsLSO() || m_ParentNBL->IsTcpCSO())
|
|
{
|
|
L4HeaderOffset = m_ParentNBL->TCPHeaderOffset();
|
|
HeadersLength = L4HeaderOffset + sizeof(TCPHeader);
|
|
Copy(Destination, HeadersLength);
|
|
}
|
|
else if (m_ParentNBL->IsUSO())
|
|
{
|
|
L4HeaderOffset = m_ParentNBL->UsoHeaderOffset();
|
|
HeadersLength = L4HeaderOffset + sizeof(UDPHeader);
|
|
HeadersLength = Copy(Destination, HeadersLength);
|
|
}
|
|
else if (m_ParentNBL->IsUdpCSO())
|
|
{
|
|
HeadersLength = Copy(Destination, MaxSize);
|
|
L4HeaderOffset = QueryL4HeaderOffset(Destination, m_Context->Offload.ipHeaderOffset);
|
|
}
|
|
else if (m_ParentNBL->IsIPHdrCSO())
|
|
{
|
|
HeadersLength = Copy(Destination, MaxSize);
|
|
L4HeaderOffset = QueryL4HeaderOffset(Destination, m_Context->Offload.ipHeaderOffset);
|
|
}
|
|
else
|
|
{
|
|
HeadersLength = ETH_HEADER_SIZE;
|
|
Copy(Destination, HeadersLength);
|
|
}
|
|
|
|
return (HeadersLength <= MaxSize);
|
|
}
|
|
|
|
void CNB::BuildPriorityHeader(PETH_HEADER EthHeader, PVLAN_HEADER VlanHeader) const
|
|
{
|
|
VlanHeader->TCI = RtlUshortByteSwap(m_ParentNBL->TCI());
|
|
|
|
if (VlanHeader->TCI != 0)
|
|
{
|
|
VlanHeader->EthType = EthHeader->EthType;
|
|
EthHeader->EthType = RtlUshortByteSwap(PRIO_HEADER_ETH_TYPE);
|
|
}
|
|
}
|
|
|
|
void CNB::PrepareOffloads(virtio_net_hdr *VirtioHeader, PVOID IpHeader, ULONG EthPayloadLength, ULONG L4HeaderOffset) const
|
|
{
|
|
*VirtioHeader = {};
|
|
|
|
if (m_ParentNBL->IsLSO())
|
|
{
|
|
SetupLSO(VirtioHeader, IpHeader, EthPayloadLength);
|
|
}
|
|
else if (m_ParentNBL->IsUSO())
|
|
{
|
|
SetupUSO(VirtioHeader, IpHeader, EthPayloadLength);
|
|
}
|
|
else if (m_ParentNBL->IsTcpCSO() || m_ParentNBL->IsUdpCSO())
|
|
{
|
|
SetupCSO(VirtioHeader, L4HeaderOffset);
|
|
}
|
|
|
|
if (m_ParentNBL->IsIPHdrCSO())
|
|
{
|
|
DoIPHdrCSO(IpHeader, EthPayloadLength);
|
|
}
|
|
}
|
|
|
|
bool CNB::BindToDescriptor(CTXDescriptor &Descriptor)
|
|
{
|
|
if (m_SGL == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Descriptor.SetNB(this);
|
|
|
|
auto &HeadersArea = Descriptor.HeadersAreaAccessor();
|
|
auto EthHeaders = HeadersArea.EthHeadersAreaVA();
|
|
ULONG HeadersLength;
|
|
ULONG L4HeaderOffset;
|
|
|
|
if (!CopyHeaders(EthHeaders, HeadersArea.MaxEthHeadersSize(), HeadersLength, L4HeaderOffset))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
BuildPriorityHeader(HeadersArea.EthHeader(), HeadersArea.VlanHeader());
|
|
PrepareOffloads(HeadersArea.VirtioHeader(),
|
|
HeadersArea.IPHeaders(),
|
|
GetDataLength() - m_Context->Offload.ipHeaderOffset,
|
|
L4HeaderOffset);
|
|
|
|
return FillDescriptorSGList(Descriptor, HeadersLength);
|
|
}
|
|
|
|
ULONG CNB::Copy(PVOID Dst, ULONG Length) const
|
|
{
|
|
ULONG CurrOffset = NET_BUFFER_CURRENT_MDL_OFFSET(m_NB);
|
|
ULONG Copied = 0;
|
|
|
|
Length = min(Length, NET_BUFFER_DATA_LENGTH(m_NB));
|
|
|
|
for (PMDL CurrMDL = NET_BUFFER_CURRENT_MDL(m_NB);
|
|
CurrMDL != nullptr && Copied < Length;
|
|
CurrMDL = CurrMDL->Next)
|
|
{
|
|
ULONG CurrLen;
|
|
PVOID CurrAddr;
|
|
|
|
#if NDIS_SUPPORT_NDIS620
|
|
NdisQueryMdl(CurrMDL, &CurrAddr, &CurrLen, MM_PAGE_PRIORITY(LowPagePriority | MdlMappingNoExecute));
|
|
#else
|
|
NdisQueryMdl(CurrMDL, &CurrAddr, &CurrLen, MM_PAGE_PRIORITY(LowPagePriority));
|
|
#endif
|
|
|
|
if (CurrAddr == nullptr)
|
|
{
|
|
break;
|
|
}
|
|
|
|
CurrLen = min(CurrLen - CurrOffset, Length - Copied);
|
|
|
|
NdisMoveMemory(RtlOffsetToPointer(Dst, Copied),
|
|
RtlOffsetToPointer(CurrAddr, CurrOffset),
|
|
CurrLen);
|
|
|
|
Copied += CurrLen;
|
|
CurrOffset = 0;
|
|
}
|
|
|
|
return Copied;
|
|
}
|