mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2426 lines
50 KiB
2426 lines
50 KiB
/*++
|
|
|
|
Copyright (c) 1990 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
interrup.c
|
|
|
|
Abstract:
|
|
|
|
This is a part of the driver for the National Semiconductor ElnkII
|
|
Ethernet controller. It contains the interrupt-handling routines.
|
|
This driver conforms to the NDIS 3.0 interface.
|
|
|
|
The overall structure and much of the code is taken from
|
|
the Lance NDIS driver by Tony Ercolano.
|
|
|
|
Author:
|
|
|
|
Sean Selitrennikoff (seanse) Dec-1991
|
|
|
|
Environment:
|
|
|
|
Kernel Mode - Or whatever is the equivalent on OS/2 and DOS.
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include <ndis.h>
|
|
#include <efilter.h>
|
|
#include "elnkhrd.h"
|
|
#include "elnksft.h"
|
|
|
|
|
|
#if DBG
|
|
#define STATIC
|
|
#else
|
|
#define STATIC static
|
|
#endif
|
|
|
|
#if DBG
|
|
extern ULONG ElnkiiSendsCompletedAfterPendOk;
|
|
extern ULONG ElnkiiSendsCompletedAfterPendFail;
|
|
#endif
|
|
|
|
|
|
UCHAR ElnkiiBroadcastAddress[ETH_LENGTH_OF_ADDRESS] =
|
|
{0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
|
|
|
|
|
//
|
|
// This is used to pad short packets.
|
|
//
|
|
|
|
static UCHAR BlankBuffer[60] = " ";
|
|
|
|
|
|
#if DBG
|
|
ULONG ElnkiiSendsIssued = 0;
|
|
ULONG ElnkiiSendsFailed = 0;
|
|
ULONG ElnkiiSendsPended = 0;
|
|
ULONG ElnkiiSendsCompletedImmediately = 0;
|
|
ULONG ElnkiiSendsCompletedAfterPendOk = 0;
|
|
ULONG ElnkiiSendsCompletedAfterPendFail = 0;
|
|
ULONG ElnkiiSendsCompletedForReset = 0;
|
|
#endif
|
|
|
|
|
|
#if DBG
|
|
|
|
#define ELNKII_LOG_SIZE 256
|
|
UCHAR ElnkiiLogBuffer[ELNKII_LOG_SIZE]={0};
|
|
UCHAR ElnkiiLogSaveBuffer[ELNKII_LOG_SIZE]={0};
|
|
UINT ElnkiiLogLoc = 0;
|
|
BOOLEAN ElnkiiLogSave = FALSE;
|
|
UINT ElnkiiLogSaveLoc = 0;
|
|
UINT ElnkiiLogSaveLeft = 0;
|
|
|
|
extern
|
|
VOID
|
|
ElnkiiLog(UCHAR c) {
|
|
|
|
ElnkiiLogBuffer[ElnkiiLogLoc++] = c;
|
|
|
|
ElnkiiLogBuffer[(ElnkiiLogLoc + 4) % ELNKII_LOG_SIZE] = '\0';
|
|
|
|
if (ElnkiiLogLoc >= ELNKII_LOG_SIZE) ElnkiiLogLoc = 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
#if DBG
|
|
|
|
#define PACKET_LIST_SIZE 256
|
|
|
|
static PNDIS_PACKET PacketList[PACKET_LIST_SIZE] = {0};
|
|
static PacketListSize = 0;
|
|
|
|
VOID
|
|
AddPacketToList(
|
|
PELNKII_ADAPTER Adapter,
|
|
PNDIS_PACKET NewPacket
|
|
)
|
|
{
|
|
INT i;
|
|
|
|
UNREFERENCED_PARAMETER(Adapter);
|
|
|
|
for (i=0; i<PacketListSize; i++) {
|
|
|
|
if (PacketList[i] == NewPacket) {
|
|
|
|
DbgPrint("dup send of %lx\n", NewPacket);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
PacketList[PacketListSize] = NewPacket;
|
|
|
|
++PacketListSize;
|
|
|
|
}
|
|
|
|
VOID
|
|
RemovePacketFromList(
|
|
PELNKII_ADAPTER Adapter,
|
|
PNDIS_PACKET OldPacket
|
|
)
|
|
{
|
|
INT i;
|
|
|
|
UNREFERENCED_PARAMETER(Adapter);
|
|
|
|
for (i=0; i<PacketListSize; i++) {
|
|
|
|
if (PacketList[i] == OldPacket) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (i == PacketListSize) {
|
|
|
|
DbgPrint("bad remove of %lx\n", OldPacket);
|
|
|
|
} else {
|
|
|
|
--PacketListSize;
|
|
|
|
PacketList[i] = PacketList[PacketListSize];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif // DBG
|
|
|
|
|
|
|
|
BOOLEAN
|
|
ElnkiiInterruptHandler(
|
|
IN PVOID ServiceContext
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the interrupt handler which is registered with the operating
|
|
system. Only one interrupt is handled at one time, even if several
|
|
are pending (i.e. transmit complete and receive).
|
|
|
|
Arguments:
|
|
|
|
ServiceContext - pointer to the adapter object
|
|
|
|
Return Value:
|
|
|
|
TRUE, if the DPC is to be executed, otherwise FALSE.
|
|
|
|
--*/
|
|
|
|
{
|
|
PELNKII_ADAPTER AdaptP = ((PELNKII_ADAPTER)ServiceContext);
|
|
|
|
IF_LOG( ElnkiiLog('i');)
|
|
IF_LOUD( DbgPrint("In ElnkISR\n");)
|
|
|
|
IF_VERY_LOUD( DbgPrint( "ElnkiiInterruptHandler entered\n" );)
|
|
|
|
if (AdaptP->InCardTest) {
|
|
|
|
//
|
|
// Ignore these random interrupts
|
|
//
|
|
|
|
IF_LOG( ElnkiiLog('I'); )
|
|
return(FALSE);
|
|
|
|
}
|
|
|
|
//
|
|
// Force the INT signal from the chip low. When the
|
|
// interrupt is acknowledged interrupts will be unblocked,
|
|
// which will cause a rising edge on the interrupt line
|
|
// if there is another interrupt pending on the card.
|
|
//
|
|
|
|
IF_LOUD( DbgPrint( " blocking interrupts\n" ); )
|
|
|
|
CardBlockInterrupts(AdaptP);
|
|
|
|
IF_LOG( ElnkiiLog('I'); )
|
|
|
|
return(TRUE);
|
|
|
|
}
|
|
|
|
VOID
|
|
ElnkiiInterruptDpc(
|
|
IN PVOID SystemSpecific1,
|
|
IN PVOID InterruptContext,
|
|
IN PVOID SystemSpecific2,
|
|
IN PVOID SystemSpecific3
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the deffered processing routine for interrupts, it examines the
|
|
'InterruptReg' to determine what deffered processing is necessary
|
|
and dispatches control to the Rcv and Xmt handlers.
|
|
|
|
Arguments:
|
|
SystemSpecific1, SystemSpecific2, SystemSpecific3 - not used
|
|
InterruptContext - a handle to the adapter block.
|
|
|
|
Return Value:
|
|
|
|
NONE.
|
|
|
|
--*/
|
|
{
|
|
PELNKII_ADAPTER AdaptP = ((PELNKII_ADAPTER)InterruptContext);
|
|
|
|
UCHAR InterruptStatus;
|
|
INTERRUPT_TYPE InterruptType;
|
|
|
|
UNREFERENCED_PARAMETER(SystemSpecific1);
|
|
UNREFERENCED_PARAMETER(SystemSpecific2);
|
|
UNREFERENCED_PARAMETER(SystemSpecific3);
|
|
|
|
IF_LOUD( DbgPrint("==>IntDpc\n");)
|
|
|
|
NdisDprAcquireSpinLock(&AdaptP->Lock);
|
|
|
|
AdaptP->References++;
|
|
|
|
//
|
|
// Get the interrupt bits
|
|
//
|
|
|
|
CardGetInterruptStatus(AdaptP, &InterruptStatus);
|
|
|
|
if (InterruptStatus != ISR_EMPTY) {
|
|
|
|
NdisRawWritePortUchar((AdaptP)->MappedIoBaseAddr+NIC_INTR_STATUS, InterruptStatus);
|
|
|
|
//
|
|
// InterruptStatus bits are used to dispatch to correct DPC and then cleared.
|
|
//
|
|
|
|
CardGetInterruptType(AdaptP,InterruptStatus, InterruptType);
|
|
|
|
} else {
|
|
|
|
InterruptType = UNKNOWN;
|
|
|
|
}
|
|
|
|
|
|
do {
|
|
|
|
while ((InterruptType != UNKNOWN) ||
|
|
((AdaptP->LoopbackQueue != NULL) &&
|
|
!(AdaptP->ReceiveInProgress || AdaptP->ResetInProgress))) {
|
|
|
|
//
|
|
// Handle interrupts
|
|
//
|
|
|
|
switch (InterruptType) {
|
|
|
|
case COUNTER:
|
|
//
|
|
// One of the counters' MSB has been set, read in all
|
|
// the values just to be sure (and then exit below).
|
|
//
|
|
|
|
IF_LOUD( DbgPrint("DPC got COUNTER\n"); )
|
|
|
|
SyncCardUpdateCounters((PVOID)AdaptP);
|
|
|
|
InterruptStatus &= ~ISR_COUNTER; //clear the COUNTER interrupt bit.
|
|
|
|
break;
|
|
|
|
case OVERFLOW:
|
|
|
|
//
|
|
// Overflow interrupts are handled as part of a receive
|
|
// interrupt, so set a flag and then pretend to be a
|
|
// receive, in case there is no receive already being handled.
|
|
//
|
|
|
|
AdaptP->BufferOverflow = TRUE;
|
|
|
|
//
|
|
// Check if a send completed before the overflow came in.
|
|
//
|
|
|
|
if (AdaptP->TransmitInterruptPending &&
|
|
!(InterruptStatus & (ISR_XMIT | ISR_XMIT_ERR))) {
|
|
|
|
IF_LOG( ElnkiiLog('|');)
|
|
|
|
InterruptStatus |= ISR_XMIT;
|
|
|
|
AdaptP->OverflowRestartXmitDpc = FALSE;
|
|
|
|
}
|
|
|
|
IF_LOUD( DbgPrint("Overflow Int\n"); )
|
|
IF_VERY_LOUD( DbgPrint( " overflow interrupt\n" ); )
|
|
|
|
InterruptStatus &= ~ISR_OVERFLOW;
|
|
|
|
|
|
case RECEIVE:
|
|
|
|
//
|
|
// For receives, call this to ensure that another interrupt
|
|
// won't happen until the driver is ready.
|
|
//
|
|
|
|
IF_LOG( ElnkiiLog('R');)
|
|
IF_LOUD( DbgPrint("DPC got RCV\n"); )
|
|
|
|
if (!AdaptP->ReceiveInProgress) {
|
|
|
|
if (ElnkiiRcvInterruptDpc(AdaptP)) {
|
|
|
|
InterruptStatus &= ~(ISR_RCV | ISR_RCV_ERR);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// We can do this because the DPC in the RcvDpc will
|
|
// handle all the interrupts.
|
|
//
|
|
|
|
InterruptStatus &= ~(ISR_RCV | ISR_RCV_ERR);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case TRANSMIT:
|
|
|
|
IF_LOG( ElnkiiLog('X');)
|
|
|
|
#if DBG
|
|
|
|
ElnkiiLogSave = FALSE;
|
|
|
|
#endif
|
|
|
|
//
|
|
// Acknowledge transmit interrupt now, because of MP systems we
|
|
// can get an interrupt from a receive that will look like a
|
|
// transmit interrupt because we haven't cleared the bit in the
|
|
// ISR. We are not concerned about multiple Receive interrupts
|
|
// since the receive handler guards against being entered twice.
|
|
//
|
|
// Since only one transmit interrupt can be pending at a time
|
|
// we know that no-one else can enter here now...
|
|
//
|
|
//
|
|
// This puts the result of the transmit in AdaptP->XmitStatus.
|
|
// SyncCardGetXmitStatus(AdaptP);
|
|
//
|
|
|
|
IF_LOUD( DbgPrint( " acking transmit interrupt\n" ); )
|
|
|
|
SyncCardGetXmitStatus(AdaptP);
|
|
|
|
AdaptP->WakeUpFoundTransmit = FALSE;
|
|
|
|
//
|
|
// This may be false if the card is currently handling an
|
|
// overflow and will restart the Dpc itself.
|
|
//
|
|
//
|
|
// If overflow handling then clear the transmit interrupt
|
|
//
|
|
|
|
ASSERT(!AdaptP->OverflowRestartXmitDpc);
|
|
|
|
if (AdaptP->ElnkiiHandleXmitCompleteRunning) {
|
|
|
|
#if DBG
|
|
DbgBreakPoint();
|
|
#endif
|
|
|
|
} else {
|
|
|
|
AdaptP->TransmitInterruptPending = FALSE;
|
|
|
|
ElnkiiXmitInterruptDpc(AdaptP);
|
|
|
|
}
|
|
|
|
IF_LOUD( DbgPrint( "DPC got XMIT\n" ); )
|
|
|
|
InterruptStatus &= ~(ISR_XMIT|ISR_XMIT_ERR);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
//
|
|
// Create a rising edge on the interrupt line.
|
|
//
|
|
|
|
IF_LOUD( DbgPrint( "unhandled interrupt type: %x", InterruptType); )
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Handle loopback
|
|
//
|
|
|
|
if ((AdaptP->LoopbackQueue != NULL) &&
|
|
!(AdaptP->ReceiveInProgress || AdaptP->ResetInProgress)) {
|
|
|
|
ElnkiiRcvInterruptDpc(AdaptP);
|
|
|
|
}
|
|
|
|
CardGetInterruptType(AdaptP,InterruptStatus, InterruptType);
|
|
|
|
}
|
|
|
|
CardGetInterruptStatus(AdaptP, &InterruptStatus);
|
|
|
|
if (InterruptStatus != ISR_EMPTY) {
|
|
|
|
NdisRawWritePortUchar((AdaptP)->MappedIoBaseAddr+NIC_INTR_STATUS, InterruptStatus);
|
|
|
|
}
|
|
|
|
CardGetInterruptType(AdaptP,InterruptStatus,InterruptType);
|
|
|
|
} while (InterruptType != UNKNOWN); // ISR says there's nothing left to do.
|
|
|
|
//
|
|
// Turn the IMR back on.
|
|
//
|
|
|
|
IF_LOUD( DbgPrint( " unblocking interrupts\n" ); )
|
|
|
|
AdaptP->NicInterruptMask = IMR_RCV | IMR_XMIT_ERR | IMR_XMIT | IMR_OVERFLOW;
|
|
|
|
CardUnblockInterrupts(AdaptP);
|
|
|
|
ELNKII_DO_DEFERRED(AdaptP);
|
|
|
|
IF_LOUD( DbgPrint("<==IntDpc\n");)
|
|
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
ElnkiiRcvInterruptDpc(
|
|
IN PELNKII_ADAPTER AdaptP
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the real interrupt handler for receive/overflow interrupt.
|
|
The ElnkiiInterruptDpc calls it directly. It calls ElnkiiHandleReceive
|
|
if that function is not already executing (since it runs at DPC, this
|
|
would only be happening on a multiprocessor system (i.e. later DPC calls
|
|
will not run until previous ones are complete on a particular processor)).
|
|
|
|
NOTE: Called with the lock held!!!
|
|
|
|
Arguments:
|
|
|
|
DeferredContext - A pointer to the adapter block.
|
|
|
|
Return Value:
|
|
|
|
TRUE if done with all receives, else FALSE
|
|
|
|
--*/
|
|
|
|
{
|
|
PELNKII_OPEN TmpOpen;
|
|
PNDIS_PACKET LPacket;
|
|
PMAC_RESERVED Reserved;
|
|
BOOLEAN TransmitInterruptWasPending = FALSE;
|
|
INDICATE_STATUS IndicateStatus = INDICATE_OK;
|
|
BOOLEAN Done = TRUE;
|
|
|
|
//
|
|
// Do nothing if a RECEIVE is already being handled.
|
|
//
|
|
|
|
IF_LOUD( DbgPrint( "ElnkiiRcvInterruptDpc entered\n" );)
|
|
|
|
AdaptP->ReceiveInProgress = TRUE;
|
|
|
|
//
|
|
// At this point receive interrupts are disabled.
|
|
//
|
|
|
|
if (!AdaptP->ResetInProgress && AdaptP->BufferOverflow) {
|
|
|
|
NdisSynchronizeWithInterrupt(
|
|
&(AdaptP->NdisInterrupt),
|
|
(PVOID)SyncCardHandleOverflow,
|
|
(PVOID)AdaptP
|
|
);
|
|
|
|
}
|
|
|
|
//
|
|
// Loop
|
|
//
|
|
|
|
SyncCardGetCurrent(AdaptP);
|
|
|
|
while (!AdaptP->ResetInProgress) {
|
|
|
|
if (AdaptP->Current != AdaptP->NicNextPacket) {
|
|
|
|
AdaptP->LoopbackPacket = (PNDIS_PACKET)NULL;
|
|
|
|
AdaptP->ReceivePacketCount++;
|
|
|
|
NdisDprReleaseSpinLock(&AdaptP->Lock);
|
|
|
|
IndicateStatus = ElnkiiIndicatePacket(AdaptP);
|
|
|
|
NdisDprAcquireSpinLock(&AdaptP->Lock);
|
|
|
|
if (IndicateStatus == CARD_BAD) {
|
|
|
|
IF_LOG( ElnkiiLog('W');)
|
|
|
|
AdaptP->NicInterruptMask = IMR_XMIT | IMR_XMIT_ERR | IMR_OVERFLOW ;
|
|
|
|
CardReset(AdaptP);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
//
|
|
// Free the space used by packet on card.
|
|
//
|
|
|
|
AdaptP->NicNextPacket = AdaptP->PacketHeader[1];
|
|
|
|
//
|
|
// This will set BOUNDARY to one behind NicNextPacket.
|
|
//
|
|
|
|
CardSetBoundary(AdaptP);
|
|
|
|
if (AdaptP->ReceivePacketCount > 10) {
|
|
|
|
//
|
|
// Give transmit interrupts a chance
|
|
//
|
|
|
|
Done = FALSE;
|
|
AdaptP->ReceivePacketCount = 0;
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
SyncCardGetCurrent(AdaptP);
|
|
|
|
if (AdaptP->Current == AdaptP->NicNextPacket) {
|
|
|
|
//
|
|
// End of loop -- no more packets
|
|
//
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
if (AdaptP->BufferOverflow) {
|
|
|
|
IF_VERY_LOUD( DbgPrint( " overflow\n" ); )
|
|
|
|
AdaptP->BufferOverflow = FALSE;
|
|
|
|
NdisSynchronizeWithInterrupt( &(AdaptP->NdisInterrupt),
|
|
(PVOID)SyncCardAcknowledgeOverflow,
|
|
(PVOID)AdaptP );
|
|
|
|
//
|
|
// Undo loopback mode
|
|
//
|
|
|
|
CardStart(AdaptP);
|
|
|
|
IF_LOG ( ElnkiiLog('f');)
|
|
|
|
//
|
|
// Check if transmission needs to be queued or not
|
|
//
|
|
|
|
if (AdaptP->OverflowRestartXmitDpc && (AdaptP->CurBufXmitting != -1)) {
|
|
|
|
IF_LOG( ElnkiiLog('?');)
|
|
|
|
AdaptP->OverflowRestartXmitDpc = FALSE;
|
|
|
|
AdaptP->WakeUpFoundTransmit = FALSE;
|
|
|
|
AdaptP->TransmitInterruptPending = TRUE;
|
|
|
|
CardStartXmit(AdaptP);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Now handle loopback packets.
|
|
//
|
|
|
|
IF_LOUD( DbgPrint( " checking loopback queue\n" );)
|
|
|
|
while (AdaptP->LoopbackQueue && !AdaptP->ResetInProgress) {
|
|
|
|
//
|
|
// Take the first packet off the loopback queue...
|
|
//
|
|
|
|
LPacket = AdaptP->LoopbackQueue;
|
|
|
|
Reserved = RESERVED(LPacket);
|
|
|
|
AdaptP->LoopbackQueue = RESERVED(AdaptP->LoopbackQueue)->NextPacket;
|
|
|
|
AdaptP->LoopbackPacket = LPacket;
|
|
|
|
AdaptP->FramesXmitGood++;
|
|
|
|
//
|
|
// Save this, since once we complete the send
|
|
// Reserved is no longer valid.
|
|
//
|
|
|
|
TmpOpen = Reserved->Open;
|
|
|
|
#if DBG
|
|
IF_ELNKIIDEBUG( ELNKII_DEBUG_CHECK_DUP_SENDS ) {
|
|
|
|
RemovePacketFromList(AdaptP, LPacket);
|
|
|
|
}
|
|
|
|
ElnkiiSendsCompletedAfterPendOk++;
|
|
#endif
|
|
|
|
//
|
|
// ... and indicate it.
|
|
//
|
|
|
|
NdisDprReleaseSpinLock(&AdaptP->Lock);
|
|
|
|
ElnkiiIndicateLoopbackPacket(AdaptP, AdaptP->LoopbackPacket);
|
|
|
|
//
|
|
// Complete the packet send.
|
|
//
|
|
|
|
NdisCompleteSend(
|
|
Reserved->Open->NdisBindingContext,
|
|
LPacket,
|
|
NDIS_STATUS_SUCCESS
|
|
);
|
|
|
|
|
|
NdisDprAcquireSpinLock(&AdaptP->Lock);
|
|
|
|
TmpOpen->ReferenceCount--;
|
|
}
|
|
|
|
//
|
|
// All receives are now done. Allow the receive indicator to run again.
|
|
//
|
|
|
|
AdaptP->ReceiveInProgress = FALSE;
|
|
|
|
if (AdaptP->ResetInProgress) {
|
|
|
|
return Done;
|
|
|
|
}
|
|
|
|
IF_LOUD( DbgPrint( " clearing ReceiveInProgress\n" );)
|
|
|
|
NdisDprReleaseSpinLock(&AdaptP->Lock);
|
|
|
|
//
|
|
// Finally, indicate ReceiveComplete to all protocols which received packets
|
|
//
|
|
|
|
EthFilterIndicateReceiveComplete(AdaptP->FilterDB);
|
|
|
|
NdisDprAcquireSpinLock(&AdaptP->Lock);
|
|
|
|
IF_LOUD( DbgPrint( "ElnkiiRcvInterruptDpc exiting\n" );)
|
|
|
|
return(Done);
|
|
|
|
}
|
|
|
|
VOID
|
|
ElnkiiXmitInterruptDpc(
|
|
IN PELNKII_ADAPTER AdaptP
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the real interrupt handler for a transmit complete interrupt.
|
|
ElnkiiInterrupt queues a call to it. It calls ElnkiiHandleXmitComplete.
|
|
|
|
NOTE : Called with the spinlock held!! and returns with it released!!!
|
|
|
|
Arguments:
|
|
|
|
AdaptP - A pointer to the adapter block.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
XMIT_BUF TmpBuf;
|
|
|
|
PNDIS_PACKET Packet;
|
|
PMAC_RESERVED Reserved;
|
|
PELNKII_OPEN TmpOpen;
|
|
|
|
IF_VERY_LOUD( DbgPrint( "ElnkiiXmitInterruptDpc entered\n" );)
|
|
|
|
AdaptP->WakeUpFoundTransmit = FALSE;
|
|
|
|
IF_LOG( ElnkiiLog('C');)
|
|
|
|
AdaptP->ElnkiiHandleXmitCompleteRunning = TRUE;
|
|
|
|
if (AdaptP->CurBufXmitting == -1)
|
|
{
|
|
AdaptP->ElnkiiHandleXmitCompleteRunning = FALSE;
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// CurBufXmitting is not -1, which means nobody else
|
|
// will touch it.
|
|
//
|
|
Packet = AdaptP->Packets[AdaptP->CurBufXmitting];
|
|
|
|
ASSERT(Packet != (PNDIS_PACKET)NULL);
|
|
|
|
Reserved = RESERVED(Packet);
|
|
|
|
IF_LOUD( DbgPrint( "packet is 0x%lx\n", Packet );)
|
|
|
|
#if DBG
|
|
|
|
if ((AdaptP->XmitStatus & TSR_XMIT_OK) == 0) {
|
|
IF_LOG(ElnkiiLog('E');)
|
|
IF_LOG(ElnkiiLog((UCHAR)AdaptP->XmitStatus);)
|
|
}
|
|
|
|
IF_ELNKIIDEBUG( ELNKII_DEBUG_CHECK_DUP_SENDS ) {
|
|
RemovePacketFromList(AdaptP, Packet);
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
if (!Reserved->Loopback) {
|
|
|
|
|
|
//
|
|
// Complete the send if it is not to be loopbacked.
|
|
//
|
|
|
|
if (AdaptP->XmitStatus & TSR_XMIT_OK) {
|
|
|
|
AdaptP->FramesXmitGood++;
|
|
|
|
#if DBG
|
|
ElnkiiSendsCompletedAfterPendOk++;
|
|
|
|
#endif
|
|
|
|
} else {
|
|
|
|
AdaptP->FramesXmitBad++;
|
|
|
|
#if DBG
|
|
ElnkiiSendsCompletedAfterPendFail++;
|
|
#endif
|
|
|
|
}
|
|
|
|
//
|
|
// Save this, since once we complete the send
|
|
// Reserved is no longer valid.
|
|
//
|
|
|
|
TmpOpen = Reserved->Open;
|
|
|
|
IF_LOG( ElnkiiLog('p');)
|
|
|
|
NdisDprReleaseSpinLock(&AdaptP->Lock);
|
|
|
|
NdisCompleteSend(Reserved->Open->NdisBindingContext,
|
|
Packet,
|
|
AdaptP->XmitStatus & TSR_XMIT_OK ?
|
|
NDIS_STATUS_SUCCESS : NDIS_STATUS_FAILURE);
|
|
|
|
NdisDprAcquireSpinLock(&AdaptP->Lock);
|
|
|
|
TmpOpen->ReferenceCount--;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Put it on the loopback queue
|
|
//
|
|
|
|
if (AdaptP->LoopbackQueue == (PNDIS_PACKET)NULL) {
|
|
|
|
AdaptP->LoopbackQueue = Packet;
|
|
AdaptP->LoopbackQTail = Packet;
|
|
|
|
} else {
|
|
|
|
RESERVED(AdaptP->LoopbackQTail)->NextPacket = Packet;
|
|
AdaptP->LoopbackQTail = Packet;
|
|
|
|
}
|
|
|
|
Reserved->NextPacket = (PNDIS_PACKET)NULL;
|
|
|
|
}
|
|
|
|
//
|
|
// Mark the current transmit as done.
|
|
//
|
|
|
|
AdaptP->Packets[AdaptP->CurBufXmitting] = (PNDIS_PACKET)NULL;
|
|
|
|
AdaptP->BufferStatus[AdaptP->CurBufXmitting] = EMPTY;
|
|
|
|
TmpBuf = NextBuf(AdaptP, AdaptP->CurBufXmitting);
|
|
|
|
//
|
|
// See what to do next.
|
|
//
|
|
|
|
switch (AdaptP->BufferStatus[TmpBuf]) {
|
|
|
|
|
|
case FULL:
|
|
|
|
//
|
|
// The next packet is ready to go -- only happens with
|
|
// more than one transmit buffer.
|
|
//
|
|
|
|
IF_LOUD( DbgPrint( " next packet ready to go\n" );)
|
|
|
|
if (AdaptP->ResetInProgress) {
|
|
|
|
//
|
|
// A reset just started, abort.
|
|
//
|
|
|
|
AdaptP->CurBufXmitting = -1;
|
|
|
|
AdaptP->BufferStatus[TmpBuf] = EMPTY; // to ack the reset
|
|
|
|
AdaptP->ElnkiiHandleXmitCompleteRunning = FALSE;
|
|
|
|
NdisDprReleaseSpinLock(&AdaptP->Lock);
|
|
|
|
ElnkiiResetStageDone(AdaptP, XMIT_STOPPED);
|
|
|
|
NdisDprAcquireSpinLock(&AdaptP->Lock);
|
|
|
|
} else {
|
|
|
|
//
|
|
// Start the transmission and check for more.
|
|
//
|
|
|
|
AdaptP->CurBufXmitting = TmpBuf;
|
|
|
|
IF_LOG( ElnkiiLog('2');)
|
|
|
|
#if DBG
|
|
|
|
ElnkiiLogSave = TRUE;
|
|
ElnkiiLogSaveLeft = 20;
|
|
|
|
#endif
|
|
|
|
AdaptP->ElnkiiHandleXmitCompleteRunning = FALSE;
|
|
|
|
//
|
|
// If we are currently handling an overflow, then we need to let
|
|
// the overflow handler send this packet...
|
|
//
|
|
|
|
if (AdaptP->BufferOverflow) {
|
|
|
|
AdaptP->OverflowRestartXmitDpc = TRUE;
|
|
|
|
IF_LOG( ElnkiiLog('O');)
|
|
|
|
} else {
|
|
|
|
//
|
|
// This is used to check if stopping the chip prevented
|
|
// a transmit complete interrupt from coming through (it
|
|
// is cleared in the ISR if a transmit DPC is queued).
|
|
//
|
|
|
|
AdaptP->TransmitInterruptPending = TRUE;
|
|
|
|
CardStartXmit(AdaptP);
|
|
|
|
}
|
|
|
|
ElnkiiCopyAndSend(AdaptP);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
case FILLING:
|
|
|
|
//
|
|
// The next packet will be started when copying down is finished.
|
|
//
|
|
|
|
IF_LOUD( DbgPrint( " next packet filling\n" );)
|
|
|
|
AdaptP->CurBufXmitting = -1;
|
|
|
|
AdaptP->NextBufToXmit = TmpBuf;
|
|
|
|
//
|
|
// If AdaptP->NextBufToFill is not TmpBuf, this
|
|
// will check to make sure NextBufToFill is not
|
|
// waiting to be filled.
|
|
//
|
|
|
|
AdaptP->ElnkiiHandleXmitCompleteRunning = FALSE;
|
|
|
|
ElnkiiCopyAndSend(AdaptP);
|
|
|
|
break;
|
|
|
|
|
|
case EMPTY:
|
|
|
|
//
|
|
// No packet is ready to transmit.
|
|
//
|
|
|
|
IF_LOUD( DbgPrint( " next packet empty\n" );)
|
|
|
|
if (AdaptP->ResetInProgress) {
|
|
|
|
//
|
|
// A reset has just started, exit.
|
|
//
|
|
|
|
AdaptP->CurBufXmitting = -1;
|
|
|
|
AdaptP->ElnkiiHandleXmitCompleteRunning = FALSE;
|
|
|
|
NdisDprReleaseSpinLock(&AdaptP->Lock);
|
|
|
|
ElnkiiResetStageDone(AdaptP, XMIT_STOPPED);
|
|
|
|
NdisDprAcquireSpinLock(&AdaptP->Lock);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
if (AdaptP->XmitQueue != (PNDIS_PACKET)NULL) {
|
|
|
|
//
|
|
// Take the packet off the head of the queue.
|
|
//
|
|
// There will be a packet on the queue with
|
|
// BufferStatus[TmpBuf] == EMPTY only when we
|
|
// have only one transmit buffer.
|
|
//
|
|
|
|
IF_LOUD( DbgPrint( " transmit queue not empty\n" );)
|
|
|
|
Packet = AdaptP->XmitQueue;
|
|
|
|
AdaptP->XmitQueue = RESERVED(AdaptP->XmitQueue)->NextPacket;
|
|
|
|
|
|
//
|
|
// At this point, NextBufToFill should equal TmpBuf.
|
|
//
|
|
|
|
AdaptP->NextBufToFill = NextBuf(AdaptP, TmpBuf);
|
|
|
|
|
|
//
|
|
// Set this now, to avoid having to get spinlock between
|
|
// copying and transmission start.
|
|
//
|
|
|
|
AdaptP->BufferStatus[TmpBuf] = FULL;
|
|
|
|
AdaptP->Packets[TmpBuf] = Packet;
|
|
AdaptP->CurBufXmitting = TmpBuf;
|
|
|
|
IF_LOG( ElnkiiLog('3');)
|
|
|
|
#if DBG
|
|
|
|
ElnkiiLogSave = TRUE;
|
|
ElnkiiLogSaveLeft = 20;
|
|
|
|
#endif
|
|
|
|
NdisDprReleaseSpinLock(&AdaptP->Lock);
|
|
|
|
|
|
//
|
|
// Copy down the data, pad short packets with blanks.
|
|
//
|
|
|
|
(VOID)CardCopyDownPacket(AdaptP, Packet, TmpBuf,
|
|
&AdaptP->PacketLens[TmpBuf]);
|
|
|
|
if (AdaptP->PacketLens[TmpBuf] < 60) {
|
|
|
|
(VOID)CardCopyDownBuffer(
|
|
AdaptP,
|
|
BlankBuffer,
|
|
TmpBuf,
|
|
AdaptP->PacketLens[TmpBuf],
|
|
60-AdaptP->PacketLens[TmpBuf]
|
|
);
|
|
|
|
}
|
|
|
|
|
|
NdisDprAcquireSpinLock(&AdaptP->Lock);
|
|
|
|
AdaptP->ElnkiiHandleXmitCompleteRunning = FALSE;
|
|
|
|
//
|
|
// If we are currently handling an overflow, then we need to let
|
|
// the overflow handler send this packet...
|
|
//
|
|
|
|
if (AdaptP->BufferOverflow) {
|
|
|
|
AdaptP->OverflowRestartXmitDpc = TRUE;
|
|
|
|
IF_LOG( ElnkiiLog('O');)
|
|
|
|
} else {
|
|
|
|
//
|
|
// This is used to check if stopping the chip prevented
|
|
// a transmit complete interrupt from coming through (it
|
|
// is cleared in the ISR if a transmit DPC is queued).
|
|
//
|
|
|
|
AdaptP->TransmitInterruptPending = TRUE;
|
|
|
|
CardStartXmit(AdaptP);
|
|
|
|
}
|
|
|
|
//
|
|
// It makes no sense to call ElnkiiCopyAndSend because
|
|
// there is only one transmit buffer, and it was just
|
|
// filled.
|
|
//
|
|
|
|
} else {
|
|
|
|
//
|
|
// No packets are waiting on the transmit queue.
|
|
//
|
|
|
|
AdaptP->CurBufXmitting = -1;
|
|
|
|
AdaptP->NextBufToXmit = TmpBuf;
|
|
|
|
AdaptP->ElnkiiHandleXmitCompleteRunning = FALSE;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
AdaptP->ElnkiiHandleXmitCompleteRunning = FALSE;
|
|
|
|
}
|
|
|
|
IF_VERY_LOUD( DbgPrint( "ElnkiiXmitInterruptDpc exiting\n" );)
|
|
|
|
}
|
|
|
|
INDICATE_STATUS
|
|
ElnkiiIndicateLoopbackPacket(
|
|
IN PELNKII_ADAPTER AdaptP,
|
|
IN PNDIS_PACKET Packet
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Indicates an NDIS_format packet to the protocols. This is used
|
|
for indicating packets from the loopback queue.
|
|
|
|
Arguments:
|
|
|
|
AdaptP - pointer to the adapter block
|
|
Packet - the packet to be indicated
|
|
|
|
Return Value:
|
|
|
|
SKIPPED if it is a run packet
|
|
INDICATE_OK otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
UINT IndicateLen;
|
|
UINT PacketLen;
|
|
|
|
//
|
|
// Indicate up to 252 bytes.
|
|
//
|
|
|
|
NdisQueryPacket(Packet,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&PacketLen
|
|
);
|
|
|
|
if (PacketLen < ETH_LENGTH_OF_ADDRESS) {
|
|
|
|
//
|
|
// A runt packet.
|
|
//
|
|
|
|
return SKIPPED;
|
|
|
|
}
|
|
|
|
IndicateLen = (PacketLen > AdaptP->MaxLookAhead) ?
|
|
AdaptP->MaxLookAhead : PacketLen;
|
|
|
|
//
|
|
// Copy the lookahead data into a contiguous buffer.
|
|
//
|
|
|
|
ElnkiiCopyOver(AdaptP->Lookahead,
|
|
Packet,
|
|
0,
|
|
IndicateLen
|
|
);
|
|
|
|
if (IndicateLen < ELNKII_HEADER_SIZE) {
|
|
|
|
//
|
|
// Must have at least the address
|
|
//
|
|
|
|
if (IndicateLen > 5) {
|
|
|
|
//
|
|
// Runt packet
|
|
//
|
|
|
|
EthFilterIndicateReceive(
|
|
AdaptP->FilterDB,
|
|
(NDIS_HANDLE)AdaptP,
|
|
(PCHAR)AdaptP->Lookahead,
|
|
AdaptP->Lookahead,
|
|
IndicateLen,
|
|
NULL,
|
|
0,
|
|
0
|
|
);
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// Indicate packet
|
|
//
|
|
|
|
EthFilterIndicateReceive(
|
|
AdaptP->FilterDB,
|
|
(NDIS_HANDLE)AdaptP,
|
|
(PCHAR)AdaptP->Lookahead,
|
|
AdaptP->Lookahead,
|
|
ELNKII_HEADER_SIZE,
|
|
AdaptP->Lookahead + ELNKII_HEADER_SIZE,
|
|
IndicateLen - ELNKII_HEADER_SIZE,
|
|
PacketLen - ELNKII_HEADER_SIZE
|
|
);
|
|
}
|
|
|
|
return INDICATE_OK;
|
|
}
|
|
|
|
UINT
|
|
ElnkiiCopyOver(
|
|
OUT PUCHAR Buf, // destination
|
|
IN PNDIS_PACKET Packet, // source packet
|
|
IN UINT Offset, // offset in packet
|
|
IN UINT Length // number of bytes to copy
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Copies bytes from a packet into a buffer. Used to copy data
|
|
out of a packet during loopback indications.
|
|
|
|
Arguments:
|
|
|
|
Buf - the destination buffer
|
|
Packet - the source packet
|
|
Offset - the offset in the packet to start copying at
|
|
Length - the number of bytes to copy
|
|
|
|
Return Value:
|
|
|
|
The actual number of bytes copied; will be less than Length if
|
|
the packet length is less than Offset+Length.
|
|
|
|
--*/
|
|
|
|
{
|
|
PNDIS_BUFFER CurBuffer;
|
|
UINT BytesCopied;
|
|
PUCHAR BufVA;
|
|
UINT BufLen;
|
|
UINT ToCopy;
|
|
UINT CurOffset;
|
|
|
|
|
|
BytesCopied = 0;
|
|
|
|
//
|
|
// First find a spot Offset bytes into the packet.
|
|
//
|
|
|
|
CurOffset = 0;
|
|
|
|
NdisQueryPacket(Packet, NULL, NULL, &CurBuffer, NULL);
|
|
|
|
while (CurBuffer != (PNDIS_BUFFER)NULL) {
|
|
|
|
NdisQueryBuffer(CurBuffer, (PVOID *)&BufVA, &BufLen);
|
|
|
|
if (CurOffset + BufLen > Offset) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
CurOffset += BufLen;
|
|
|
|
NdisGetNextBuffer(CurBuffer, &CurBuffer);
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// See if the end of the packet has already been passed.
|
|
//
|
|
|
|
if (CurBuffer == (PNDIS_BUFFER)NULL) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// Now copy over Length bytes.
|
|
//
|
|
|
|
BufVA += (Offset - CurOffset);
|
|
|
|
BufLen -= (Offset - CurOffset);
|
|
|
|
for (;;) {
|
|
|
|
ToCopy = (BytesCopied+BufLen > Length) ? Length - BytesCopied : BufLen;
|
|
|
|
ELNKII_MOVE_MEM(Buf+BytesCopied, BufVA, ToCopy);
|
|
|
|
BytesCopied += ToCopy;
|
|
|
|
|
|
if (BytesCopied == Length) {
|
|
|
|
return BytesCopied;
|
|
|
|
}
|
|
|
|
NdisGetNextBuffer(CurBuffer, &CurBuffer);
|
|
|
|
if (CurBuffer == (PNDIS_BUFFER)NULL) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
NdisQueryBuffer(CurBuffer, (PVOID *)&BufVA, &BufLen);
|
|
|
|
}
|
|
|
|
return BytesCopied;
|
|
|
|
}
|
|
|
|
INDICATE_STATUS
|
|
ElnkiiIndicatePacket(
|
|
IN PELNKII_ADAPTER AdaptP
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Indicates the first packet on the card to the protocols.
|
|
|
|
Arguments:
|
|
|
|
AdaptP - pointer to the adapter block.
|
|
|
|
Return Value:
|
|
|
|
CARD_BAD if the card should be reset;
|
|
INDICATE_OK otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
UINT PacketLen;
|
|
PUCHAR PacketLoc;
|
|
PUCHAR IndicateBuf;
|
|
UINT IndicateLen;
|
|
UCHAR PossibleNextPacket1, PossibleNextPacket2;
|
|
|
|
|
|
//
|
|
// First copy up the four-byte header the card attaches.
|
|
//
|
|
|
|
PacketLoc = AdaptP->PageStart +
|
|
256*(AdaptP->NicNextPacket-AdaptP->NicPageStart);
|
|
|
|
|
|
if (!CardCopyUp(AdaptP, AdaptP->PacketHeader, PacketLoc, 4))
|
|
return(CARD_BAD);
|
|
|
|
//
|
|
// Check if the next packet byte agress with the length, as
|
|
// described on p. A-3 of the Etherlink II Technical Reference.
|
|
// The start of the packet plus the MSB of the length must
|
|
// be equal to the start of the next packet minus one or two.
|
|
// Otherwise the header is considered corrupted, and the
|
|
// card must be reset.
|
|
//
|
|
|
|
PossibleNextPacket1 =
|
|
AdaptP->NicNextPacket + AdaptP->PacketHeader[3] + (UCHAR)1;
|
|
|
|
if (PossibleNextPacket1 >= AdaptP->NicPageStop) {
|
|
|
|
PossibleNextPacket1 -= (AdaptP->NicPageStop - AdaptP->NicPageStart);
|
|
|
|
}
|
|
|
|
if (PossibleNextPacket1 != AdaptP->PacketHeader[1]) {
|
|
|
|
PossibleNextPacket2 = PossibleNextPacket1+(UCHAR)1;
|
|
|
|
if (PossibleNextPacket2 == AdaptP->NicPageStop) {
|
|
|
|
PossibleNextPacket2 = AdaptP->NicPageStart;
|
|
|
|
}
|
|
|
|
if (PossibleNextPacket2 != AdaptP->PacketHeader[1]) {
|
|
|
|
IF_LOUD(DbgPrint("F");)
|
|
|
|
if ((AdaptP->PacketHeader[1] < AdaptP->NicPageStart) ||
|
|
(AdaptP->PacketHeader[1] >= AdaptP->NicPageStop)) {
|
|
|
|
//
|
|
// We return CARD_BAD because the Dpc will set the NicNextPacket
|
|
// pointer based on the PacketHeader[1] value if we return
|
|
// SKIPPED.
|
|
//
|
|
|
|
return(CARD_BAD);
|
|
|
|
}
|
|
|
|
return SKIPPED;
|
|
}
|
|
|
|
}
|
|
|
|
#if DBG
|
|
|
|
IF_ELNKIIDEBUG( ELNKII_DEBUG_WORKAROUND1 ) {
|
|
//
|
|
// Now check for the high order 2 bits being set, as described
|
|
// on page A-2 of the Etherlink II Technical Reference. If either
|
|
// of the two high order bits is set in the receive status byte
|
|
// in the packet header, the packet should be skipped (but
|
|
// the adapter does not need to be reset).
|
|
//
|
|
|
|
if (AdaptP->PacketHeader[0] & (RSR_DISABLED|RSR_DEFERRING)) {
|
|
|
|
IF_LOUD (DbgPrint("H");)
|
|
|
|
return SKIPPED;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
//
|
|
// Packet length is in bytes 3 and 4 of the header.
|
|
//
|
|
PacketLen = AdaptP->PacketHeader[2] + AdaptP->PacketHeader[3] * 256;
|
|
if (0 == PacketLen)
|
|
{
|
|
//
|
|
// Packet with no data...
|
|
//
|
|
IndicateLen = 0;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Don't count the header.
|
|
//
|
|
PacketLen -= 4;
|
|
|
|
//
|
|
// See how much to indicate (252 bytes max).
|
|
//
|
|
IndicateLen = PacketLen < AdaptP->MaxLookAhead ?
|
|
PacketLen : AdaptP->MaxLookAhead;
|
|
}
|
|
|
|
//
|
|
// Save the length with the adapter block.
|
|
//
|
|
AdaptP->PacketLen = PacketLen;
|
|
|
|
//
|
|
// if not memory mapped, have to copy the lookahead data up first.
|
|
//
|
|
if (!AdaptP->MemMapped)
|
|
{
|
|
if (!CardCopyUp(AdaptP, AdaptP->Lookahead, PacketLoc + 4, IndicateLen))
|
|
return(CARD_BAD);
|
|
|
|
IndicateBuf = AdaptP->Lookahead;
|
|
}
|
|
else
|
|
{
|
|
if (IndicateLen != 0)
|
|
{
|
|
NdisCreateLookaheadBufferFromSharedMemory(
|
|
(PVOID)(PacketLoc + 4),
|
|
IndicateLen,
|
|
&IndicateBuf
|
|
);
|
|
}
|
|
}
|
|
|
|
if (IndicateBuf != NULL)
|
|
{
|
|
AdaptP->FramesRcvGood++;
|
|
|
|
if (IndicateLen < ELNKII_HEADER_SIZE)
|
|
{
|
|
//
|
|
// Indicate packet
|
|
//
|
|
EthFilterIndicateReceive(
|
|
AdaptP->FilterDB,
|
|
(NDIS_HANDLE)AdaptP,
|
|
(PCHAR)IndicateBuf,
|
|
IndicateBuf,
|
|
IndicateLen,
|
|
NULL,
|
|
0,
|
|
0
|
|
);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Indicate packet
|
|
//
|
|
EthFilterIndicateReceive(
|
|
AdaptP->FilterDB,
|
|
(NDIS_HANDLE)AdaptP,
|
|
(PCHAR)IndicateBuf,
|
|
IndicateBuf,
|
|
ELNKII_HEADER_SIZE,
|
|
IndicateBuf + ELNKII_HEADER_SIZE,
|
|
IndicateLen - ELNKII_HEADER_SIZE,
|
|
PacketLen - ELNKII_HEADER_SIZE
|
|
);
|
|
}
|
|
|
|
if (AdaptP->MemMapped && (IndicateLen != 0))
|
|
{
|
|
NdisDestroyLookaheadBufferFromSharedMemory(IndicateBuf);
|
|
}
|
|
}
|
|
|
|
return(INDICATE_OK);
|
|
}
|
|
|
|
NDIS_STATUS
|
|
ElnkiiTransferData(
|
|
IN NDIS_HANDLE MacBindingHandle,
|
|
IN NDIS_HANDLE MacReceiveContext,
|
|
IN UINT ByteOffset,
|
|
IN UINT BytesToTransfer,
|
|
OUT PNDIS_PACKET Packet,
|
|
OUT PUINT BytesTransferred
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
NDIS function.
|
|
|
|
Arguments:
|
|
|
|
see NDIS 3.0 spec.
|
|
|
|
Notes:
|
|
|
|
- The MacReceiveContext will be a pointer to the open block for
|
|
the packet.
|
|
- The LoopbackPacket field in the adapter block will be NULL if this
|
|
is a call for a normal packet, otherwise it will be set to point
|
|
to the loopback packet.
|
|
|
|
--*/
|
|
|
|
{
|
|
UINT BytesLeft, BytesNow, BytesWanted;
|
|
PUCHAR CurCardLoc;
|
|
PNDIS_BUFFER CurBuffer;
|
|
PUCHAR BufVA, BufStart;
|
|
UINT BufLen, BufOff, Copied;
|
|
UINT CurOff;
|
|
PELNKII_ADAPTER AdaptP = ((PELNKII_ADAPTER)MacReceiveContext);
|
|
|
|
UNREFERENCED_PARAMETER(MacBindingHandle);
|
|
|
|
//
|
|
// Determine whether this was a loopback indication.
|
|
//
|
|
|
|
ByteOffset += ELNKII_HEADER_SIZE;
|
|
|
|
if (AdaptP->LoopbackPacket != (PNDIS_PACKET)NULL) {
|
|
|
|
//
|
|
// Yes, have to copy data from AdaptP->LoopbackPacket into Packet.
|
|
//
|
|
|
|
NdisQueryPacket(Packet, NULL, NULL, &CurBuffer, NULL);
|
|
|
|
CurOff = ByteOffset;
|
|
|
|
while (CurBuffer != (PNDIS_BUFFER)NULL) {
|
|
|
|
NdisQueryBuffer(CurBuffer, (PVOID *)&BufVA, &BufLen);
|
|
|
|
Copied =
|
|
ElnkiiCopyOver(BufVA, AdaptP->LoopbackPacket, CurOff, BufLen);
|
|
|
|
CurOff += Copied;
|
|
|
|
if (Copied < BufLen) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
NdisGetNextBuffer(CurBuffer, &CurBuffer);
|
|
|
|
}
|
|
|
|
//
|
|
// We are done, return.
|
|
//
|
|
|
|
|
|
*BytesTransferred = CurOff - ByteOffset;
|
|
if (*BytesTransferred > BytesToTransfer) {
|
|
*BytesTransferred = BytesToTransfer;
|
|
}
|
|
|
|
return NDIS_STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// This was NOT a loopback packet, get the data off the card.
|
|
//
|
|
|
|
//
|
|
// See how much data there is to transfer.
|
|
//
|
|
|
|
if (ByteOffset+BytesToTransfer > AdaptP->PacketLen) {
|
|
|
|
BytesWanted = AdaptP->PacketLen - ByteOffset;
|
|
|
|
} else {
|
|
|
|
BytesWanted = BytesToTransfer;
|
|
|
|
}
|
|
|
|
BytesLeft = BytesWanted;
|
|
|
|
|
|
//
|
|
// Determine where the copying should start.
|
|
//
|
|
|
|
CurCardLoc = AdaptP->PageStart +
|
|
256*(AdaptP->NicNextPacket-AdaptP->NicPageStart) +
|
|
4 + ByteOffset;
|
|
|
|
if (CurCardLoc > AdaptP->PageStop) {
|
|
|
|
CurCardLoc = CurCardLoc - (AdaptP->PageStop - AdaptP->PageStart);
|
|
|
|
}
|
|
|
|
|
|
NdisQueryPacket(Packet, NULL, NULL, &CurBuffer, NULL);
|
|
|
|
NdisQueryBuffer(CurBuffer, (PVOID *)&BufStart, &BufLen);
|
|
|
|
BufOff = 0;
|
|
|
|
//
|
|
// Loop, filling each buffer in the packet until there
|
|
// are no more buffers or the data has all been copied.
|
|
//
|
|
|
|
while (BytesLeft > 0) {
|
|
|
|
//
|
|
// See how much data to read into this buffer.
|
|
//
|
|
|
|
if ((BufLen-BufOff) > BytesLeft) {
|
|
|
|
BytesNow = BytesLeft;
|
|
|
|
} else {
|
|
|
|
BytesNow = (BufLen - BufOff);
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// See if the data for this buffer wraps around the end
|
|
// of the receive buffers (if so filling this buffer
|
|
// will use two iterations of the loop).
|
|
//
|
|
|
|
if (CurCardLoc + BytesNow > AdaptP->PageStop) {
|
|
|
|
BytesNow = AdaptP->PageStop - CurCardLoc;
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// Copy up the data.
|
|
//
|
|
|
|
if (!CardCopyUp(AdaptP, BufStart+BufOff, CurCardLoc, BytesNow))
|
|
{
|
|
*BytesTransferred = BytesWanted - BytesLeft;
|
|
|
|
return(NDIS_STATUS_FAILURE);
|
|
}
|
|
|
|
CurCardLoc += BytesNow;
|
|
|
|
BytesLeft -= BytesNow;
|
|
|
|
|
|
//
|
|
// Is the transfer done now?
|
|
//
|
|
|
|
if (BytesLeft == 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// Wrap around the end of the receive buffers?
|
|
//
|
|
|
|
if (CurCardLoc == AdaptP->PageStop) {
|
|
|
|
CurCardLoc = AdaptP->PageStart;
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// Was the end of this packet buffer reached?
|
|
//
|
|
|
|
BufOff += BytesNow;
|
|
|
|
if (BufOff == BufLen) {
|
|
|
|
NdisGetNextBuffer(CurBuffer, &CurBuffer);
|
|
|
|
if (CurBuffer == (PNDIS_BUFFER)NULL) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
NdisQueryBuffer(CurBuffer, (PVOID *)&BufStart, &BufLen);
|
|
|
|
BufOff = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
*BytesTransferred = BytesWanted - BytesLeft;
|
|
|
|
return NDIS_STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NDIS_STATUS
|
|
ElnkiiSend(
|
|
IN NDIS_HANDLE MacBindingHandle,
|
|
IN PNDIS_PACKET Packet
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
NDIS function.
|
|
|
|
Arguments:
|
|
|
|
See NDIS 3.0 spec.
|
|
|
|
Notes:
|
|
|
|
|
|
--*/
|
|
|
|
{
|
|
PELNKII_OPEN OpenP = ((PELNKII_OPEN)MacBindingHandle);
|
|
PELNKII_ADAPTER AdaptP = OpenP->Adapter;
|
|
PMAC_RESERVED Reserved = RESERVED(Packet);
|
|
|
|
//
|
|
// First Buffer
|
|
//
|
|
PNDIS_BUFFER FirstBuffer;
|
|
|
|
//
|
|
// Virtual address of first buffer
|
|
//
|
|
PVOID BufferVA;
|
|
|
|
//
|
|
// Length of the first buffer
|
|
//
|
|
UINT Length;
|
|
|
|
|
|
NdisAcquireSpinLock(&AdaptP->Lock);
|
|
|
|
#if DBG
|
|
ElnkiiSendsIssued++;
|
|
#endif
|
|
|
|
//
|
|
// Ensure that the open won't close during this function.
|
|
//
|
|
|
|
if (OpenP->Closing) {
|
|
|
|
#if DBG
|
|
ElnkiiSendsFailed++;
|
|
#endif
|
|
|
|
NdisReleaseSpinLock(&AdaptP->Lock);
|
|
|
|
return NDIS_STATUS_CLOSING;
|
|
}
|
|
|
|
//
|
|
// All requests are rejected during a reset.
|
|
//
|
|
|
|
if (AdaptP->ResetInProgress) {
|
|
|
|
NdisReleaseSpinLock(&AdaptP->Lock);
|
|
|
|
#if DBG
|
|
ElnkiiSendsFailed++;
|
|
#endif
|
|
|
|
return NDIS_STATUS_RESET_IN_PROGRESS;
|
|
|
|
}
|
|
|
|
OpenP->ReferenceCount++;
|
|
|
|
AdaptP->References++;
|
|
|
|
//
|
|
// Set up the MacReserved section of the packet.
|
|
//
|
|
|
|
Reserved->Open = (PELNKII_OPEN)MacBindingHandle;
|
|
Reserved->NextPacket = (PNDIS_PACKET)NULL;
|
|
|
|
#if DBG
|
|
IF_ELNKIIDEBUG( ELNKII_DEBUG_CHECK_DUP_SENDS ) {
|
|
|
|
AddPacketToList(AdaptP, Packet);
|
|
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Set Reserved->Loopback.
|
|
//
|
|
|
|
NdisQueryPacket(Packet, NULL, NULL, &FirstBuffer, NULL);
|
|
|
|
//
|
|
// Get VA of first buffer
|
|
//
|
|
|
|
NdisQueryBuffer(
|
|
FirstBuffer,
|
|
&BufferVA,
|
|
&Length
|
|
);
|
|
|
|
if (OpenP->ProtOptionFlags & NDIS_PROT_OPTION_NO_LOOPBACK){
|
|
|
|
Reserved->Loopback = FALSE;
|
|
|
|
} else{
|
|
|
|
Reserved->Loopback = EthShouldAddressLoopBack(AdaptP->FilterDB, BufferVA);
|
|
|
|
}
|
|
|
|
//
|
|
// Put it on the loopback queue only. All packets go through the
|
|
// loopback queue first, and then on to the xmit queue.
|
|
//
|
|
|
|
IF_LOG( ElnkiiLog('D');)
|
|
|
|
#if DBG
|
|
ElnkiiSendsPended++;
|
|
#endif
|
|
|
|
|
|
//
|
|
// We do not OpenP->ReferenceCount-- because that will be done when
|
|
// then send completes.
|
|
//
|
|
|
|
//
|
|
// Put Packet on queue to hit the wire.
|
|
//
|
|
|
|
IF_VERY_LOUD( DbgPrint("Putting 0x%x on, after 0x%x\n",Packet,AdaptP->XmitQTail); )
|
|
|
|
if (AdaptP->XmitQueue != NULL) {
|
|
|
|
RESERVED(AdaptP->XmitQTail)->NextPacket = Packet;
|
|
|
|
AdaptP->XmitQTail = Packet;
|
|
|
|
} else {
|
|
|
|
AdaptP->XmitQueue = Packet;
|
|
|
|
AdaptP->XmitQTail = Packet;
|
|
|
|
}
|
|
|
|
Reserved->NextPacket = NULL;
|
|
|
|
ELNKII_DO_DEFERRED(AdaptP);
|
|
|
|
return NDIS_STATUS_PENDING;
|
|
}
|
|
|
|
UINT
|
|
ElnkiiCompareMemory(
|
|
IN PUCHAR String1,
|
|
IN PUCHAR String2,
|
|
IN UINT Length
|
|
)
|
|
{
|
|
UINT i;
|
|
|
|
for (i=0; i<Length; i++) {
|
|
if (String1[i] != String2[i]) {
|
|
return (UINT)(-1);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
VOID
|
|
ElnkiiCopyAndSend(
|
|
IN PELNKII_ADAPTER AdaptP
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Copies packets from the transmit queue to the board and starts
|
|
transmission as long as there is data waiting. Must be called
|
|
with Lock held.
|
|
|
|
Arguments:
|
|
|
|
AdaptP - pointer to the adapter block
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
XMIT_BUF TmpBuf1;
|
|
PNDIS_PACKET Packet;
|
|
|
|
|
|
//
|
|
// Loop as long as there is data on the transmit queue
|
|
// and space for it on the card.
|
|
//
|
|
|
|
while ((AdaptP->BufferStatus[TmpBuf1=AdaptP->NextBufToFill] == EMPTY) &&
|
|
(AdaptP->XmitQueue != NULL)) {
|
|
|
|
//
|
|
// Take the packet off of the transmit queue.
|
|
//
|
|
|
|
IF_VERY_LOUD( DbgPrint("Removing 0x%x, New Head is 0x%x\n",Packet,RESERVED(Packet)->NextPacket); )
|
|
|
|
Packet = AdaptP->XmitQueue;
|
|
|
|
AdaptP->XmitQueue = RESERVED(Packet)->NextPacket;
|
|
|
|
AdaptP->BufferStatus[TmpBuf1] = FILLING;
|
|
|
|
AdaptP->Packets[TmpBuf1] = Packet;
|
|
|
|
AdaptP->NextBufToFill = NextBuf(AdaptP, TmpBuf1);
|
|
|
|
NdisReleaseSpinLock(&AdaptP->Lock);
|
|
|
|
|
|
//
|
|
// copy down the data, pad short packets with blanks.
|
|
//
|
|
|
|
(VOID)CardCopyDownPacket(AdaptP, Packet, TmpBuf1,
|
|
&AdaptP->PacketLens[TmpBuf1]);
|
|
|
|
if (AdaptP->PacketLens[TmpBuf1] < 60) {
|
|
|
|
(VOID)CardCopyDownBuffer(
|
|
AdaptP,
|
|
BlankBuffer,
|
|
TmpBuf1,
|
|
AdaptP->PacketLens[TmpBuf1],
|
|
60 - AdaptP->PacketLens[TmpBuf1]);
|
|
}
|
|
|
|
NdisAcquireSpinLock(&AdaptP->Lock);
|
|
|
|
if (AdaptP->ResetInProgress)
|
|
{
|
|
PELNKII_OPEN TmpOpen = (PELNKII_OPEN)((RESERVED(Packet)->Open));
|
|
|
|
//
|
|
// A reset just started, abort.
|
|
//
|
|
//
|
|
AdaptP->BufferStatus[TmpBuf1] = EMPTY; // to ack the reset
|
|
|
|
NdisReleaseSpinLock(&AdaptP->Lock);
|
|
|
|
//
|
|
// Complete the send.
|
|
//
|
|
NdisCompleteSend(
|
|
RESERVED(Packet)->Open->NdisBindingContext,
|
|
Packet,
|
|
NDIS_STATUS_SUCCESS
|
|
);
|
|
|
|
ElnkiiResetStageDone(AdaptP, BUFFERS_EMPTY);
|
|
|
|
NdisAcquireSpinLock(&AdaptP->Lock);
|
|
|
|
TmpOpen->ReferenceCount--;
|
|
|
|
return;
|
|
}
|
|
|
|
AdaptP->BufferStatus[TmpBuf1] = FULL;
|
|
|
|
|
|
//
|
|
// See whether to start the transmission.
|
|
//
|
|
|
|
if (AdaptP->CurBufXmitting != -1) {
|
|
|
|
//
|
|
// Another transmit is still in progress.
|
|
//
|
|
|
|
continue;
|
|
}
|
|
|
|
if (AdaptP->NextBufToXmit != TmpBuf1) {
|
|
|
|
//
|
|
// A packet ahead of us is being copied down, this
|
|
// transmission can't be started now.
|
|
//
|
|
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// OK to start transmission.
|
|
//
|
|
|
|
AdaptP->CurBufXmitting = AdaptP->NextBufToXmit;
|
|
|
|
|
|
IF_LOG( ElnkiiLog('4');)
|
|
|
|
#if DBG
|
|
|
|
ElnkiiLogSave = TRUE;
|
|
ElnkiiLogSaveLeft = 20;
|
|
|
|
#endif
|
|
|
|
//
|
|
// If we are currently handling an overflow, then we need to let
|
|
// the overflow handler send this packet...
|
|
//
|
|
|
|
if (AdaptP->BufferOverflow) {
|
|
|
|
AdaptP->OverflowRestartXmitDpc = TRUE;
|
|
|
|
IF_LOG( ElnkiiLog('O');)
|
|
|
|
} else {
|
|
|
|
//
|
|
// This is used to check if stopping the chip prevented
|
|
// a transmit complete interrupt from coming through (it
|
|
// is cleared in the ISR if a transmit DPC is queued).
|
|
//
|
|
|
|
AdaptP->TransmitInterruptPending = TRUE;
|
|
|
|
CardStartXmit(AdaptP);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
VOID
|
|
ElnkiiWakeUpDpc(
|
|
IN PVOID SystemSpecific1,
|
|
IN PVOID Context,
|
|
IN PVOID SystemSpecific2,
|
|
IN PVOID SystemSpecific3
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This DPC routine is queued every 2 seconds to check on the
|
|
transmit queue. If a transmit interrupt was not received
|
|
in the last two seconds and there is a transmit in progress,
|
|
then we complete the transmit.
|
|
|
|
Arguments:
|
|
|
|
Context - Really a pointer to the adapter.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
PELNKII_ADAPTER AdaptP = (PELNKII_ADAPTER)Context;
|
|
XMIT_BUF TmpBuf;
|
|
PMAC_RESERVED Reserved;
|
|
PELNKII_OPEN TmpOpen;
|
|
PNDIS_PACKET Packet;
|
|
PNDIS_PACKET NextPacket;
|
|
|
|
UNREFERENCED_PARAMETER(SystemSpecific1);
|
|
UNREFERENCED_PARAMETER(SystemSpecific2);
|
|
UNREFERENCED_PARAMETER(SystemSpecific3);
|
|
|
|
NdisDprAcquireSpinLock(&AdaptP->Lock);
|
|
|
|
if ((AdaptP->WakeUpFoundTransmit) &&
|
|
(AdaptP->CurBufXmitting != -1))
|
|
{
|
|
//
|
|
// We had a transmit pending the last time we ran,
|
|
// and it has not been completed...we need to complete
|
|
// it now.
|
|
|
|
AdaptP->TransmitInterruptPending = FALSE;
|
|
AdaptP->WakeUpFoundTransmit = FALSE;
|
|
|
|
IF_LOG( ElnkiiLog('K');)
|
|
|
|
//
|
|
// We log the first 5 of these.
|
|
//
|
|
if (AdaptP->TimeoutCount < 5)
|
|
{
|
|
NdisWriteErrorLogEntry(
|
|
AdaptP->NdisAdapterHandle,
|
|
NDIS_ERROR_CODE_HARDWARE_FAILURE,
|
|
2,
|
|
0x3,
|
|
AdaptP->TimeoutCount
|
|
);
|
|
}
|
|
|
|
AdaptP->TimeoutCount++;
|
|
|
|
#if DBG
|
|
|
|
ELNKII_MOVE_MEM(ElnkiiLogSaveBuffer, ElnkiiLogBuffer, ELNKII_LOG_SIZE);
|
|
|
|
ElnkiiLogSave = FALSE;
|
|
ElnkiiLogSaveLoc = ElnkiiLogLoc;
|
|
|
|
#endif
|
|
|
|
//
|
|
// We stop and start the card, then queue a DPC to
|
|
// handle the receive.
|
|
//
|
|
|
|
CardStop(AdaptP);
|
|
|
|
Packet = AdaptP->Packets[AdaptP->CurBufXmitting];
|
|
|
|
AdaptP->Packets[AdaptP->CurBufXmitting] = (PNDIS_PACKET)NULL;
|
|
|
|
AdaptP->BufferStatus[AdaptP->CurBufXmitting] = EMPTY;
|
|
|
|
TmpBuf = NextBuf(AdaptP, AdaptP->CurBufXmitting);
|
|
|
|
//
|
|
// Set this so that we don't access the packets
|
|
// somewhere else.
|
|
//
|
|
AdaptP->CurBufXmitting = -1;
|
|
|
|
//
|
|
// Abort all sends
|
|
//
|
|
while (Packet != NULL) {
|
|
|
|
Reserved = RESERVED(Packet);
|
|
|
|
TmpOpen = Reserved->Open;
|
|
|
|
NdisDprReleaseSpinLock(&AdaptP->Lock);
|
|
|
|
NdisCompleteSend(
|
|
TmpOpen->NdisBindingContext,
|
|
Packet,
|
|
NDIS_STATUS_SUCCESS
|
|
);
|
|
|
|
NdisDprAcquireSpinLock(&AdaptP->Lock);
|
|
|
|
TmpOpen->ReferenceCount--;
|
|
|
|
//
|
|
// Get next packet
|
|
//
|
|
|
|
if (AdaptP->BufferStatus[TmpBuf] == FULL) {
|
|
|
|
Packet = AdaptP->Packets[TmpBuf];
|
|
|
|
AdaptP->Packets[TmpBuf] = NULL;
|
|
|
|
AdaptP->BufferStatus[TmpBuf] = EMPTY;
|
|
|
|
TmpBuf = NextBuf(AdaptP, TmpBuf);
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Set send variables correctly.
|
|
//
|
|
AdaptP->NextBufToXmit = TmpBuf;
|
|
|
|
|
|
|
|
Packet = AdaptP->XmitQueue;
|
|
|
|
AdaptP->XmitQueue = NULL;
|
|
|
|
while (Packet != NULL) {
|
|
|
|
Reserved = RESERVED(Packet);
|
|
|
|
//
|
|
// Remove the packet from the queue.
|
|
//
|
|
|
|
NextPacket = Reserved->NextPacket;
|
|
|
|
TmpOpen = Reserved->Open;
|
|
|
|
NdisDprReleaseSpinLock(&AdaptP->Lock);
|
|
|
|
NdisCompleteSend(
|
|
TmpOpen->NdisBindingContext,
|
|
Packet,
|
|
NDIS_STATUS_SUCCESS
|
|
);
|
|
|
|
NdisDprAcquireSpinLock(&AdaptP->Lock);
|
|
|
|
TmpOpen->ReferenceCount--;
|
|
|
|
Packet = NextPacket;
|
|
|
|
}
|
|
|
|
//
|
|
// Restart the card
|
|
//
|
|
|
|
CardStart(AdaptP);
|
|
|
|
NdisDprReleaseSpinLock(&AdaptP->Lock);
|
|
|
|
} else {
|
|
|
|
if (AdaptP->CurBufXmitting != -1) {
|
|
|
|
AdaptP->WakeUpFoundTransmit = TRUE;
|
|
|
|
IF_LOG( ElnkiiLog('L');)
|
|
|
|
}
|
|
|
|
NdisDprReleaseSpinLock(&AdaptP->Lock);
|
|
|
|
|
|
}
|
|
|
|
//
|
|
// Fire off another Dpc to execute after 2 seconds
|
|
//
|
|
|
|
NdisSetTimer(
|
|
&AdaptP->WakeUpTimer,
|
|
2000
|
|
);
|
|
|
|
}
|
|
|