/* * PROJECT: ReactOS nVidia nForce Ethernet Controller Driver * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) * PURPOSE: Interrupt handling * COPYRIGHT: Copyright 2021-2022 Dmitry Borisov */ /* INCLUDES *******************************************************************/ #include "nvnet.h" #define NDEBUG #include "debug.h" /* FUNCTIONS ******************************************************************/ ULONG ProcessTransmitDescriptorsLegacy( _In_ PNVNET_ADAPTER Adapter, _Inout_ PLIST_ENTRY SendReadyList) { PNVNET_TCB Tcb = Adapter->Send.LastTcb; ULONG TcbProcessed = 0; while (Tcb != Adapter->Send.CurrentTcb) { NVNET_TBD Tbd = Tcb->Tbd; ULONG Flags = Tbd.x32->FlagsLength; if (Flags & NV_TX_VALID) break; NDIS_DbgPrint(MIN_TRACE, ("Packet transmitted (flags %lx)\n", Flags & FLAG_MASK_V1)); if (Flags & NV_TX_ERROR) { ++Adapter->Statistics.TransmitErrors; if (Flags & NV_TX_UNDERFLOW) ++Adapter->Statistics.TransmitUnderrunErrors; if (Flags & NV_TX_LATECOLLISION) ++Adapter->Statistics.TransmitLateCollisions; if (Flags & NV_TX_CARRIERLOST) ++Adapter->Statistics.TransmitLostCarrierSense; if (Flags & NV_TX_RETRYERROR) { ++Adapter->Statistics.TransmitExcessiveCollisions; if (!(Flags & NV_TX_RETRYCOUNT_MASK)) { NvNetBackoffReseed(Adapter); } } } else { ++Adapter->Statistics.TransmitOk; if (Flags & NV_TX_DEFERRED) { ++Adapter->Statistics.TransmitDeferred; } if (!(Flags & NV_TX_RETRYCOUNT_MASK)) ++Adapter->Statistics.TransmitZeroRetry; else if ((Flags & NV_TX_RETRYCOUNT_MASK) == NV_TX_ONE_RETRY) ++Adapter->Statistics.TransmitOneRetry; } InsertTailList(SendReadyList, PACKET_ENTRY(Tcb->Packet)); NV_RELEASE_TCB(Adapter, Tcb); ++TcbProcessed; Tcb = NV_NEXT_TCB(Adapter, Tcb); } Adapter->Send.LastTcb = Tcb; return TcbProcessed; } ULONG ProcessTransmitDescriptors32( _In_ PNVNET_ADAPTER Adapter, _Inout_ PLIST_ENTRY SendReadyList) { PNVNET_TCB Tcb = Adapter->Send.LastTcb; ULONG TcbProcessed = 0; while (Tcb != Adapter->Send.CurrentTcb) { NVNET_TBD Tbd = Tcb->Tbd; ULONG Flags = Tbd.x32->FlagsLength; if (Flags & NV_TX_VALID) break; NDIS_DbgPrint(MIN_TRACE, ("Packet transmitted (flags %lx)\n", Flags & FLAG_MASK_V2)); if (Flags & NV_TX2_ERROR) { if ((Flags & NV_TX2_RETRYERROR) && !(Flags & NV_TX2_RETRYCOUNT_MASK)) { if (Adapter->Features & DEV_HAS_GEAR_MODE) NvNetBackoffReseedEx(Adapter); else NvNetBackoffReseed(Adapter); } } InsertTailList(SendReadyList, PACKET_ENTRY(Tcb->Packet)); NV_RELEASE_TCB(Adapter, Tcb); ++TcbProcessed; Tcb = NV_NEXT_TCB(Adapter, Tcb); } Adapter->Send.LastTcb = Tcb; return TcbProcessed; } ULONG ProcessTransmitDescriptors64( _In_ PNVNET_ADAPTER Adapter, _Inout_ PLIST_ENTRY SendReadyList) { PNVNET_TCB Tcb = Adapter->Send.LastTcb; ULONG TcbProcessed = 0; while (Tcb != Adapter->Send.CurrentTcb) { NVNET_TBD Tbd = Tcb->Tbd; ULONG Flags = Tbd.x64->FlagsLength; if (Flags & NV_TX_VALID) break; if (Adapter->Flags & NV_SEND_ERRATA_PRESENT) { PNVNET_TCB DeferredTcb; --Adapter->Send.PacketsCount; DeferredTcb = Adapter->Send.DeferredTcb; if (DeferredTcb) { DeferredTcb->DeferredTbd.x64->FlagsLength |= NV_TX2_VALID; ++Adapter->Send.PacketsCount; Adapter->Send.DeferredTcb = NV_NEXT_TCB(Adapter, DeferredTcb); if (Adapter->Send.DeferredTcb == Adapter->Send.CurrentTcb) { Adapter->Send.DeferredTcb = NULL; } NV_WRITE(Adapter, NvRegTxRxControl, Adapter->TxRxControl | NVREG_TXRXCTL_KICK); } } NDIS_DbgPrint(MIN_TRACE, ("Packet transmitted (flags %lx)\n", Flags & FLAG_MASK_V2)); if (Flags & NV_TX2_ERROR) { if ((Flags & NV_TX2_RETRYERROR) && !(Flags & NV_TX2_RETRYCOUNT_MASK)) { if (Adapter->Features & DEV_HAS_GEAR_MODE) NvNetBackoffReseedEx(Adapter); else NvNetBackoffReseed(Adapter); } } InsertTailList(SendReadyList, PACKET_ENTRY(Tcb->Packet)); NV_RELEASE_TCB(Adapter, Tcb); ++TcbProcessed; Tcb = NV_NEXT_TCB(Adapter, Tcb); } Adapter->Send.LastTcb = Tcb; return TcbProcessed; } static BOOLEAN HandleLengthError( _In_ PVOID EthHeader, _Inout_ PUSHORT Length) { PUCHAR Buffer = EthHeader; ULONG i; DBG_UNREFERENCED_LOCAL_VARIABLE(Buffer); /* TODO */ NDIS_DbgPrint(MAX_TRACE, ("() Length error detected (%u): \n", *Length)); for (i = 0; i < *Length; ++i) { NDIS_DbgPrint(MAX_TRACE, ("%02x ", Buffer[i])); } NDIS_DbgPrint(MAX_TRACE, ("\n\n*** Please report it to the team! ***\n\n")); return FALSE; } /* TODO: This need to be rewritten. I leave it as-is for now */ static ULONG ProcessReceiveDescriptors( _In_ PNVNET_ADAPTER Adapter, _In_ ULONG TotalRxProcessed) { ULONG i, RxProcessed = 0; BOOLEAN IndicateComplete = FALSE; for (i = 0; i < NVNET_RECEIVE_DESCRIPTORS; ++i) { ULONG Flags; USHORT Length; PUCHAR EthHeader; NV_RBD NvRbd; if (Adapter->Features & DEV_HAS_HIGH_DMA) { NvRbd.x64 = &Adapter->Receive.NvRbd.x64[Adapter->CurrentRx]; Flags = NvRbd.x64->FlagsLength; } else { NvRbd.x32 = &Adapter->Receive.NvRbd.x32[Adapter->CurrentRx]; Flags = NvRbd.x32->FlagsLength; } if (Flags & NV_RX_AVAIL) break; if (TotalRxProcessed + RxProcessed >= NVNET_RECEIVE_PROCESSING_LIMIT) break; if (!Adapter->PacketFilter) goto NextDescriptor; if (Adapter->Features & (DEV_HAS_HIGH_DMA | DEV_HAS_LARGEDESC)) { if (!(Flags & NV_RX2_DESCRIPTORVALID)) goto NextDescriptor; Length = Flags & LEN_MASK_V2; EthHeader = &Adapter->ReceiveBuffer[Adapter->CurrentRx * NVNET_RECEIVE_BUFFER_SIZE]; if (Flags & NV_RX2_ERROR) { if ((Flags & NV_RX2_ERROR_MASK) == NV_RX2_ERROR4) { if (!HandleLengthError(EthHeader, &Length)) goto NextDescriptor; } else if ((Flags & NV_RX2_ERROR_MASK) == NV_RX2_FRAMINGERR) { if (Flags & NV_RX2_SUBTRACT1) --Length; } else { goto NextDescriptor; } } NDIS_DbgPrint(MIN_TRACE, ("Packet %d received (length %d, flags %lx)\n", Adapter->CurrentRx, Length, Flags & FLAG_MASK_V2)); } else { if (!(Flags & NV_RX_DESCRIPTORVALID)) goto NextDescriptor; Length = Flags & LEN_MASK_V1; EthHeader = &Adapter->ReceiveBuffer[Adapter->CurrentRx * NVNET_RECEIVE_BUFFER_SIZE]; if (Flags & NV_RX_ERROR) { if ((Flags & NV_RX_ERROR_MASK) == NV_RX_ERROR4) { if (!HandleLengthError(EthHeader, &Length)) goto NextDescriptor; } else if ((Flags & NV_RX_ERROR_MASK) == NV_RX_FRAMINGERR) { if (Flags & NV_RX_SUBTRACT1) --Length; } else { ++Adapter->Statistics.ReceiveErrors; if (Flags & NV_RX_MISSEDFRAME) ++Adapter->Statistics.ReceiveNoBuffers; if (Flags & NV_RX_FRAMINGERR) ++Adapter->Statistics.ReceiveAlignmentErrors; if (Flags & NV_RX_OVERFLOW) ++Adapter->Statistics.ReceiveOverrunErrors; if (Flags & NV_RX_CRCERR) ++Adapter->Statistics.ReceiveCrcErrors; goto NextDescriptor; } } ++Adapter->Statistics.ReceiveOk; NDIS_DbgPrint(MIN_TRACE, ("Packet %d received (length %d, flags %lx)\n", Adapter->CurrentRx, Length, Flags & FLAG_MASK_V1)); } NdisMEthIndicateReceive(Adapter->AdapterHandle, NULL, (PCHAR)EthHeader, sizeof(ETH_HEADER), EthHeader + sizeof(ETH_HEADER), Length - sizeof(ETH_HEADER), Length - sizeof(ETH_HEADER)); IndicateComplete = TRUE; NextDescriptor: /* Invalidate the buffer length and release the descriptor */ if (Adapter->Features & DEV_HAS_HIGH_DMA) NvRbd.x64->FlagsLength = NV_RX2_AVAIL | NVNET_RECEIVE_BUFFER_SIZE; else NvRbd.x32->FlagsLength = NV_RX_AVAIL | NVNET_RECEIVE_BUFFER_SIZE; Adapter->CurrentRx = (Adapter->CurrentRx + 1) % NVNET_RECEIVE_DESCRIPTORS; ++RxProcessed; } if (IndicateComplete) { NdisMEthIndicateReceiveComplete(Adapter->AdapterHandle); } return RxProcessed; } static inline VOID ChangeInterruptMode( _In_ PNVNET_ADAPTER Adapter, _In_ ULONG Workload) { if (Workload > NVNET_IM_THRESHOLD) { Adapter->InterruptIdleCount = 0; /* High activity, polling based strategy */ Adapter->InterruptMask = NVREG_IRQMASK_CPU; } else { if (Adapter->InterruptIdleCount < NVNET_IM_MAX_IDLE) { ++Adapter->InterruptIdleCount; } else { /* Low activity, 1 interrupt per packet */ Adapter->InterruptMask = NVREG_IRQMASK_THROUGHPUT; } } } static VOID HandleLinkStateChange( _In_ PNVNET_ADAPTER Adapter) { ULONG MiiStatus; BOOLEAN Connected, Report = FALSE; NDIS_DbgPrint(MIN_TRACE, ("()\n")); NdisDprAcquireSpinLock(&Adapter->Lock); MiiStatus = NV_READ(Adapter, NvRegMIIStatus); /* Clear the link change interrupt */ NV_WRITE(Adapter, NvRegMIIStatus, NVREG_MIISTAT_LINKCHANGE); if (MiiStatus & NVREG_MIISTAT_LINKCHANGE) { Connected = NvNetUpdateLinkSpeed(Adapter); if (Adapter->Connected != Connected) { Adapter->Connected = Connected; Report = TRUE; if (Connected) { /* Link up */ NvNetToggleClockPowerGating(Adapter, FALSE); NdisDprAcquireSpinLock(&Adapter->Receive.Lock); NvNetStartReceiver(Adapter); } else { /* Link down */ NvNetToggleClockPowerGating(Adapter, TRUE); NdisDprAcquireSpinLock(&Adapter->Receive.Lock); NvNetStopReceiver(Adapter); } NdisDprReleaseSpinLock(&Adapter->Receive.Lock); } } NdisDprReleaseSpinLock(&Adapter->Lock); if (Report) { NdisMIndicateStatus(Adapter->AdapterHandle, Connected ? NDIS_STATUS_MEDIA_CONNECT : NDIS_STATUS_MEDIA_DISCONNECT, NULL, 0); NdisMIndicateStatusComplete(Adapter->AdapterHandle); } } static VOID HandleRecoverableError( _In_ PNVNET_ADAPTER Adapter) { /* TODO */ NDIS_DbgPrint(MAX_TRACE, ("() Recoverable error detected\n")); } VOID NTAPI MiniportHandleInterrupt( _In_ NDIS_HANDLE MiniportAdapterContext) { PNVNET_ADAPTER Adapter = (PNVNET_ADAPTER)MiniportAdapterContext; ULONG InterruptStatus = Adapter->InterruptStatus; ULONG RxProcessed, TotalTxProcessed = 0, TotalRxProcessed = 0; LIST_ENTRY SendReadyList; NDIS_DbgPrint(MIN_TRACE, ("() Events 0x%lx\n", InterruptStatus)); if (!(Adapter->Flags & NV_ACTIVE)) return; InitializeListHead(&SendReadyList); /* Process the rings and measure network activity */ while (TotalRxProcessed < NVNET_RECEIVE_PROCESSING_LIMIT) { NdisDprAcquireSpinLock(&Adapter->Send.Lock); TotalTxProcessed += Adapter->ProcessTransmit(Adapter, &SendReadyList); NdisDprReleaseSpinLock(&Adapter->Send.Lock); while (!IsListEmpty(&SendReadyList)) { PLIST_ENTRY Entry = RemoveHeadList(&SendReadyList); NdisMSendComplete(Adapter->AdapterHandle, CONTAINING_RECORD(Entry, NDIS_PACKET, MiniportReserved), NDIS_STATUS_SUCCESS); } RxProcessed = ProcessReceiveDescriptors(Adapter, TotalRxProcessed); if (!RxProcessed) break; TotalRxProcessed += RxProcessed; } NDIS_DbgPrint(MIN_TRACE, ("Total TX: %d, RX: %d\n", TotalTxProcessed, TotalRxProcessed)); /* Moderate the interrupts */ if (Adapter->OptimizationMode == NV_OPTIMIZATION_MODE_DYNAMIC) { ChangeInterruptMode(Adapter, TotalTxProcessed + TotalRxProcessed); } if (InterruptStatus & NVREG_IRQ_RX_NOBUF) { ++Adapter->Statistics.ReceiveIrqNoBuffers; } if (InterruptStatus & NVREG_IRQ_LINK) { HandleLinkStateChange(Adapter); } if (InterruptStatus & NVREG_IRQ_RECOVER_ERROR) { HandleRecoverableError(Adapter); } /* Enable interrupts on the NIC */ NvNetApplyInterruptMask(Adapter); } VOID NTAPI MiniportISR( _Out_ PBOOLEAN InterruptRecognized, _Out_ PBOOLEAN QueueMiniportHandleInterrupt, _In_ NDIS_HANDLE MiniportAdapterContext) { PNVNET_ADAPTER Adapter = (PNVNET_ADAPTER)MiniportAdapterContext; ULONG InterruptStatus; NDIS_DbgPrint(MIN_TRACE, ("()\n")); InterruptStatus = NV_READ(Adapter, NvRegIrqStatus); /* Clear any interrupt events */ NV_WRITE(Adapter, NvRegIrqStatus, InterruptStatus); if (InterruptStatus & Adapter->InterruptMask) { /* Disable further interrupts */ NvNetDisableInterrupts(Adapter); Adapter->InterruptStatus = InterruptStatus; *InterruptRecognized = TRUE; *QueueMiniportHandleInterrupt = TRUE; } else { /* This interrupt is not ours */ *InterruptRecognized = FALSE; *QueueMiniportHandleInterrupt = FALSE; } }