[NVNET] Add driver for nForce-based NICs
[reactos.git] / drivers / network / dd / nvnet / send.c
diff --git a/drivers/network/dd/nvnet/send.c b/drivers/network/dd/nvnet/send.c
new file mode 100644 (file)
index 0000000..60d8afb
--- /dev/null
@@ -0,0 +1,524 @@
+/*
+ * PROJECT:     ReactOS nVidia nForce Ethernet Controller Driver
+ * LICENSE:     GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
+ * PURPOSE:     Packet sending
+ * COPYRIGHT:   Copyright 2021-2022 Dmitry Borisov <di.sean@protonmail.com>
+ */
+
+/* INCLUDES *******************************************************************/
+
+#include "nvnet.h"
+
+#define NDEBUG
+#include "debug.h"
+
+/* FUNCTIONS ******************************************************************/
+
+VOID
+NvNetTransmitPacket32(
+    _In_ PNVNET_ADAPTER Adapter,
+    _In_ PNVNET_TCB Tcb,
+    _In_ PSCATTER_GATHER_LIST SgList)
+{
+    NVNET_TBD Tbd, LastTbd;
+    ULONG i, Flags;
+    ULONG Slots;
+
+    Flags = 0;
+    Slots = 0;
+    Tbd = Adapter->Send.CurrentTbd;
+
+    for (i = 0; i < SgList->NumberOfElements; ++i)
+    {
+        ULONG Address = NdisGetPhysicalAddressLow(SgList->Elements[i].Address);
+        ULONG Length = SgList->Elements[i].Length;
+
+        if (Length > NV_MAXIMUM_SG_SIZE)
+        {
+            ULONG ImplicitEntries = NV_IMPLICIT_ENTRIES(Length);
+
+            do
+            {
+                ++Slots;
+
+                Tbd.x32->Address = Address;
+                Tbd.x32->FlagsLength = Flags | (NV_MAXIMUM_SG_SIZE - 1);
+                LastTbd = Tbd;
+                Tbd = NV_NEXT_TBD_32(Adapter, Tbd);
+
+                Flags = NV_TX_VALID;
+
+                Length -= NV_MAXIMUM_SG_SIZE;
+                Address += NV_MAXIMUM_SG_SIZE;
+
+                --ImplicitEntries;
+            }
+            while (ImplicitEntries);
+        }
+
+        ++Slots;
+
+        Tbd.x32->Address = Address;
+        Tbd.x32->FlagsLength = Flags | (Length - 1);
+        LastTbd = Tbd;
+        Tbd = NV_NEXT_TBD_32(Adapter, Tbd);
+
+        Flags = NV_TX_VALID;
+    }
+
+    Tcb->Slots = Slots;
+    Tcb->Tbd = LastTbd;
+
+    if (Adapter->Features & DEV_HAS_LARGEDESC)
+    {
+        LastTbd.x32->FlagsLength |= NV_TX2_LASTPACKET;
+    }
+    else
+    {
+        LastTbd.x32->FlagsLength |= NV_TX_LASTPACKET;
+    }
+
+    if (Tcb->Flags & NV_TCB_LARGE_SEND)
+    {
+        Flags |= (Tcb->Mss << NV_TX2_TSO_SHIFT) | NV_TX2_TSO;
+    }
+    else
+    {
+        if (Tcb->Flags & NV_TCB_CHECKSUM_IP)
+        {
+            Flags |= NV_TX2_CHECKSUM_L3;
+        }
+        if (Tcb->Flags & (NV_TCB_CHECKSUM_TCP | NV_TCB_CHECKSUM_UDP))
+        {
+            Flags |= NV_TX2_CHECKSUM_L4;
+        }
+    }
+
+    Adapter->Send.CurrentTbd.x32->FlagsLength |= Flags;
+    Adapter->Send.CurrentTbd = Tbd;
+
+    NV_WRITE(Adapter, NvRegTxRxControl, Adapter->TxRxControl | NVREG_TXRXCTL_KICK);
+}
+
+VOID
+NvNetTransmitPacket64(
+    _In_ PNVNET_ADAPTER Adapter,
+    _In_ PNVNET_TCB Tcb,
+    _In_ PSCATTER_GATHER_LIST SgList)
+{
+    NVNET_TBD Tbd, LastTbd;
+    ULONG i, Flags;
+    ULONG Slots;
+
+    Flags = 0;
+    Slots = 0;
+    Tbd = Adapter->Send.CurrentTbd;
+
+    for (i = 0; i < SgList->NumberOfElements; ++i)
+    {
+        ULONG64 Address = SgList->Elements[i].Address.QuadPart;
+        ULONG Length = SgList->Elements[i].Length;
+
+        if (Length > NV_MAXIMUM_SG_SIZE)
+        {
+            ULONG ImplicitEntries = NV_IMPLICIT_ENTRIES(Length);
+
+            do
+            {
+                ++Slots;
+
+                Tbd.x64->AddressLow = (ULONG)Address;
+                Tbd.x64->AddressHigh = Address >> 32;
+                Tbd.x64->VlanTag = 0;
+                Tbd.x64->FlagsLength = Flags | (NV_MAXIMUM_SG_SIZE - 1);
+                LastTbd = Tbd;
+                Tbd = NV_NEXT_TBD_64(Adapter, Tbd);
+
+                Flags = NV_TX2_VALID;
+
+                Length -= NV_MAXIMUM_SG_SIZE;
+                Address += NV_MAXIMUM_SG_SIZE;
+
+                --ImplicitEntries;
+            }
+            while (ImplicitEntries);
+        }
+
+        ++Slots;
+
+        Tbd.x64->AddressLow = (ULONG)Address;
+        Tbd.x64->AddressHigh = Address >> 32;
+        Tbd.x64->VlanTag = 0;
+        Tbd.x64->FlagsLength = Flags | (Length - 1);
+        LastTbd = Tbd;
+        Tbd = NV_NEXT_TBD_64(Adapter, Tbd);
+
+        Flags = NV_TX2_VALID;
+    }
+
+    Tcb->Slots = Slots;
+    Tcb->Tbd = LastTbd;
+
+    LastTbd.x64->FlagsLength |= NV_TX2_LASTPACKET;
+
+    if (Adapter->Flags & NV_SEND_ERRATA_PRESENT)
+    {
+        if (Adapter->Send.PacketsCount == NV_TX_LIMIT_COUNT)
+        {
+            Tcb->DeferredTbd = Adapter->Send.CurrentTbd;
+
+            if (!Adapter->Send.DeferredTcb)
+            {
+                Adapter->Send.DeferredTcb = Tcb;
+            }
+
+            Flags = 0;
+        }
+        else
+        {
+            ++Adapter->Send.PacketsCount;
+        }
+    }
+
+    if (Tcb->Flags & NV_TCB_LARGE_SEND)
+    {
+        Flags |= (Tcb->Mss << NV_TX2_TSO_SHIFT) | NV_TX2_TSO;
+    }
+    else
+    {
+        if (Tcb->Flags & NV_TCB_CHECKSUM_IP)
+        {
+            Flags |= NV_TX2_CHECKSUM_L3;
+        }
+        if (Tcb->Flags & (NV_TCB_CHECKSUM_TCP | NV_TCB_CHECKSUM_UDP))
+        {
+            Flags |= NV_TX2_CHECKSUM_L4;
+        }
+    }
+
+    // Adapter->Send.CurrentTbd.x64->VlanTag = NV_TX3_VLAN_TAG_PRESENT; TODO
+    Adapter->Send.CurrentTbd.x64->FlagsLength |= Flags;
+    Adapter->Send.CurrentTbd = Tbd;
+
+    NV_WRITE(Adapter, NvRegTxRxControl, Adapter->TxRxControl | NVREG_TXRXCTL_KICK);
+}
+
+static
+DECLSPEC_NOINLINE
+ULONG
+NvNetQueryTcpIpHeaders(
+    _In_ PNVNET_ADAPTER Adapter,
+    _In_ PNDIS_PACKET Packet)
+{
+    PNDIS_BUFFER CurrentBuffer;
+    PVOID Address;
+    UINT CurrentLength;
+    UINT PacketLength;
+    PIPv4_HEADER IpHeader;
+    PTCPv4_HEADER TcpHeader;
+    ULONG BytesCopied = 0;
+    UCHAR Buffer[136];
+
+    NdisGetFirstBufferFromPacketSafe(Packet,
+                                     &CurrentBuffer,
+                                     &Address,
+                                     &CurrentLength,
+                                     &PacketLength,
+                                     HighPagePriority);
+    if (!Address)
+        return 0;
+
+    while (TRUE)
+    {
+        CurrentLength = min(CurrentLength, sizeof(Buffer) - BytesCopied);
+
+        NdisMoveMemory(&Buffer[BytesCopied], Address, CurrentLength);
+        BytesCopied += CurrentLength;
+
+        if (BytesCopied >= sizeof(Buffer))
+            break;
+
+        NdisGetNextBuffer(CurrentBuffer, &CurrentBuffer);
+
+        if (!CurrentBuffer)
+            return 0;
+
+        NdisQueryBufferSafe(CurrentBuffer,
+                            &Address,
+                            &CurrentLength,
+                            HighPagePriority);
+    }
+
+    IpHeader = (PIPv4_HEADER)&Buffer[Adapter->IpHeaderOffset];
+    TcpHeader = (PTCPv4_HEADER)((PUCHAR)IpHeader + IP_HEADER_LENGTH(IpHeader));
+
+    return IP_HEADER_LENGTH(IpHeader) + TCP_HEADER_LENGTH(TcpHeader);
+}
+
+static
+BOOLEAN
+NvNetCopyPacket(
+    _In_ PNVNET_ADAPTER Adapter,
+    _In_ PNDIS_PACKET Packet,
+    _In_ PNVNET_TX_BUFFER Buffer)
+{
+    PNDIS_BUFFER CurrentBuffer;
+    PVOID Address;
+    UINT CurrentLength;
+    UINT PacketLength;
+    PUCHAR Destination;
+
+    NdisGetFirstBufferFromPacketSafe(Packet,
+                                     &CurrentBuffer,
+                                     &Address,
+                                     &CurrentLength,
+                                     &PacketLength,
+                                     HighPagePriority);
+    if (!Address)
+        return FALSE;
+
+    Destination = Buffer->VirtualAddress;
+
+    while (TRUE)
+    {
+        NdisMoveMemory(Destination, Address, CurrentLength);
+        Destination += CurrentLength;
+
+        NdisGetNextBuffer(CurrentBuffer, &CurrentBuffer);
+
+        if (!CurrentBuffer)
+            break;
+
+        NdisQueryBufferSafe(CurrentBuffer,
+                            &Address,
+                            &CurrentLength,
+                            HighPagePriority);
+        if (!Address)
+            return FALSE;
+    }
+
+    return TRUE;
+}
+
+static
+NDIS_STATUS
+NvNetSendPacketLargeSend(
+    _In_ PNVNET_ADAPTER Adapter,
+    _In_ PNDIS_PACKET Packet,
+    _In_ ULONG TotalLength)
+{
+    PSCATTER_GATHER_LIST SgList;
+    ULONG Mss, Length;
+    PNVNET_TCB Tcb;
+
+    if (!Adapter->Send.TcbSlots)
+    {
+        return NDIS_STATUS_RESOURCES;
+    }
+
+    SgList = NDIS_PER_PACKET_INFO_FROM_PACKET(Packet, ScatterGatherListPacketInfo);
+
+    /* Make sure we have room to setup all fragments */
+    C_ASSERT(NVNET_TRANSMIT_DESCRIPTORS > ((NVNET_MAXIMUM_LSO_FRAME_SIZE / PAGE_SIZE) + 3));
+    ASSERT(SgList->NumberOfElements +
+           (NVNET_MAXIMUM_LSO_FRAME_SIZE / (NV_MAXIMUM_SG_SIZE + 1)) <=
+           NVNET_TRANSMIT_DESCRIPTORS);
+
+    if (SgList->NumberOfElements +
+        (NVNET_MAXIMUM_LSO_FRAME_SIZE / (NV_MAXIMUM_SG_SIZE + 1)) < Adapter->Send.TbdSlots)
+    {
+        return NDIS_STATUS_RESOURCES;
+    }
+
+    Length = NvNetQueryTcpIpHeaders(Adapter, Packet);
+    if (!Length)
+    {
+        return NDIS_STATUS_RESOURCES;
+    }
+
+    NDIS_PER_PACKET_INFO_FROM_PACKET(Packet, TcpLargeSendPacketInfo) =
+        UlongToPtr(TotalLength - Adapter->IpHeaderOffset - Length);
+
+    --Adapter->Send.TcbSlots;
+
+    Mss = PtrToUlong(NDIS_PER_PACKET_INFO_FROM_PACKET(Packet, TcpLargeSendPacketInfo));
+
+    Tcb = Adapter->Send.CurrentTcb;
+    Tcb->Mss = Mss;
+    Tcb->Packet = Packet;
+    Tcb->Flags = NV_TCB_LARGE_SEND;
+
+    Adapter->TransmitPacket(Adapter, Tcb, SgList);
+
+    ASSERT(Adapter->Send.TbdSlots >= Tcb->Slots);
+    Adapter->Send.TbdSlots -= Tcb->Slots;
+
+    Adapter->Send.CurrentTcb = NV_NEXT_TCB(Adapter, Tcb);
+
+    return NDIS_STATUS_SUCCESS;
+}
+
+static
+ULONG
+NvNetGetChecksumInfo(
+    _In_ PNVNET_ADAPTER Adapter,
+    _In_ PNDIS_PACKET Packet)
+{
+    ULONG Flags;
+    NDIS_TCP_IP_CHECKSUM_PACKET_INFO ChecksumInfo;
+
+    if (NDIS_GET_PACKET_PROTOCOL_TYPE(Packet) != NDIS_PROTOCOL_ID_TCP_IP)
+        return 0;
+
+    ChecksumInfo.Value = PtrToUlong(NDIS_PER_PACKET_INFO_FROM_PACKET(Packet,
+                                                                     TcpIpChecksumPacketInfo));
+
+    Flags = 0;
+    if (ChecksumInfo.Transmit.NdisPacketChecksumV4)
+    {
+        if (ChecksumInfo.Transmit.NdisPacketTcpChecksum && Adapter->Offload.SendTcpChecksum)
+        {
+            Flags |= NV_TCB_CHECKSUM_TCP;
+        }
+        if (ChecksumInfo.Transmit.NdisPacketUdpChecksum && Adapter->Offload.SendUdpChecksum)
+        {
+            Flags |= NV_TCB_CHECKSUM_UDP;
+        }
+        if (ChecksumInfo.Transmit.NdisPacketIpChecksum && Adapter->Offload.SendIpChecksum)
+        {
+            Flags |= NV_TCB_CHECKSUM_IP;
+        }
+    }
+
+    return Flags;
+}
+
+static
+NDIS_STATUS
+NvNetSendPacket(
+    _In_ PNVNET_ADAPTER Adapter,
+    _In_ PNDIS_PACKET Packet,
+    _In_ ULONG TotalLength)
+{
+    PSCATTER_GATHER_LIST SgList;
+    SCATTER_GATHER_LIST LocalSgList;
+    PNVNET_TCB Tcb;
+    ULONG Flags;
+
+    ASSERT(TotalLength <= Adapter->MaximumFrameSize);
+
+    if (!Adapter->Send.TcbSlots)
+    {
+        return NDIS_STATUS_RESOURCES;
+    }
+
+    Flags = NvNetGetChecksumInfo(Adapter, Packet);
+
+    SgList = NDIS_PER_PACKET_INFO_FROM_PACKET(Packet, ScatterGatherListPacketInfo);
+
+    if (SgList->NumberOfElements > NVNET_FRAGMENTATION_THRESHOLD)
+    {
+        if (!Adapter->Send.TbdSlots || !Adapter->Send.BufferList.Next)
+        {
+            return NDIS_STATUS_RESOURCES;
+        }
+        else
+        {
+            PNVNET_TX_BUFFER CoalesceBuffer;
+            BOOLEAN Success;
+
+            --Adapter->Send.TcbSlots;
+
+            CoalesceBuffer = (PNVNET_TX_BUFFER)PopEntryList(&Adapter->Send.BufferList);
+
+            NdisDprReleaseSpinLock(&Adapter->Send.Lock);
+
+            Success = NvNetCopyPacket(Adapter, Packet, CoalesceBuffer);
+
+            NdisDprAcquireSpinLock(&Adapter->Send.Lock);
+
+            if (!Success || !Adapter->Send.TbdSlots || !(Adapter->Flags & NV_ACTIVE))
+            {
+                PushEntryList(&Adapter->Send.BufferList, &CoalesceBuffer->Link);
+
+                ++Adapter->Send.TcbSlots;
+
+                return NDIS_STATUS_RESOURCES;
+            }
+
+            Flags |= NV_TCB_COALESCE;
+
+            LocalSgList.NumberOfElements = 1;
+            LocalSgList.Elements[0].Address = CoalesceBuffer->PhysicalAddress;
+            LocalSgList.Elements[0].Length = TotalLength;
+            SgList = &LocalSgList;
+
+            Tcb = Adapter->Send.CurrentTcb;
+            Tcb->Buffer = CoalesceBuffer;
+        }
+    }
+    else
+    {
+        if (SgList->NumberOfElements +
+            (NVNET_MAXIMUM_FRAME_SIZE_JUMBO / (NV_MAXIMUM_SG_SIZE + 1)) > Adapter->Send.TbdSlots)
+        {
+            return NDIS_STATUS_RESOURCES;
+        }
+
+        --Adapter->Send.TcbSlots;
+
+        Tcb = Adapter->Send.CurrentTcb;
+    }
+
+    Tcb->Packet = Packet;
+    Tcb->Flags = Flags;
+
+    Adapter->TransmitPacket(Adapter, Tcb, SgList);
+
+    ASSERT(Adapter->Send.TbdSlots >= Tcb->Slots);
+    Adapter->Send.TbdSlots -= Tcb->Slots;
+
+    Adapter->Send.CurrentTcb = NV_NEXT_TCB(Adapter, Tcb);
+
+    return NDIS_STATUS_PENDING;
+}
+
+/* FIXME: Use the proper send function (MiniportSendPackets) */
+NDIS_STATUS
+NTAPI
+MiniportSend(
+    _In_ NDIS_HANDLE MiniportAdapterContext,
+    _In_ PNDIS_PACKET Packet,
+    _In_ UINT Flags)
+{
+    PNVNET_ADAPTER Adapter = (PNVNET_ADAPTER)MiniportAdapterContext;
+    UINT TotalLength;
+    NDIS_STATUS Status;
+
+    NDIS_DbgPrint(MIN_TRACE, ("()\n"));
+
+    NdisQueryPacketLength(Packet, &TotalLength);
+
+    NdisDprAcquireSpinLock(&Adapter->Send.Lock);
+
+    if (!(Adapter->Flags & NV_ACTIVE))
+    {
+        NdisDprReleaseSpinLock(&Adapter->Send.Lock);
+
+        return NDIS_STATUS_FAILURE;
+    }
+
+    if (Adapter->Flags & NV_SEND_LARGE_SEND &&
+        PtrToUlong(NDIS_PER_PACKET_INFO_FROM_PACKET(Packet, TcpLargeSendPacketInfo)))
+    {
+        Status = NvNetSendPacketLargeSend(Adapter, Packet, TotalLength);
+    }
+    else
+    {
+        Status = NvNetSendPacket(Adapter, Packet, TotalLength);
+    }
+
+    NdisDprReleaseSpinLock(&Adapter->Send.Lock);
+
+    return Status;
+}