xbox-kernel/private/ntos/net/nicx.cpp
2020-09-30 17:17:25 +02:00

1229 lines
34 KiB
C++

// ---------------------------------------------------------------------------------------
// nicx.cpp
//
// Copyright (C) Microsoft Corporation
// ---------------------------------------------------------------------------------------
#include "xnp.h"
#include "xnver.h"
#ifdef XNET_FEATURE_XBOX
// ---------------------------------------------------------------------------------------
// Globals
// ---------------------------------------------------------------------------------------
KINTERRUPT CXnNic::s_InterruptObject;
// ---------------------------------------------------------------------------------------
// Definitions
// ---------------------------------------------------------------------------------------
#define HWADDR0(_hwaddr) ((const DWORD *)((BYTE *)(_hwaddr)))[0]
#define HWADDR1(_hwaddr) ((const WORD *)((BYTE *)(_hwaddr)))[2]
DefineTag(nicStats, TAG_ENABLE); // Trace Nic stats
DefineTag(nicStatsAll, 0); // Trace all Nic stats
// ---------------------------------------------------------------------------------------
// Internal
// ---------------------------------------------------------------------------------------
void CXnNic::NicStartXmitRecv()
{
ICHECK(NIC, USER|UDPC|SDPC);
PNICCSR pCsr = PNicCsr();
pCsr->rx_poll = RXPOLL_EN | _dwRxPollFreq;
pCsr->tx_en = TXEN_ENABLE;
pCsr->rx_en = RXEN_ENABLE;
pCsr->mode = MODE_TXDM|MODE_RXDM;
}
void CXnNic::NicStopXmitRecv()
{
ICHECK(NIC, USER|UDPC|SDPC);
PNICCSR pCsr = PNicCsr();
int timeout;
// Turn off the transmitter and receiver
pCsr->rx_poll = 0;
pCsr->rx_en = 0;
pCsr->tx_en = 0;
// Wait for a max of 5msec until both the transmitter and receiver are idle
for (timeout=500; timeout--; )
{
if ( !(pCsr->rx_sta & RXSTA_BUSY)
&& !(pCsr->tx_sta & TXSTA_BUSY))
break;
KeStallExecutionProcessor(10);
}
// Ensure there is no active DMA transfer in progress
pCsr->mode = MODE_DISABLE_DMA;
for (timeout=500; timeout--; )
{
if (pCsr->mode & MODE_DMA_IDLE)
break;
KeStallExecutionProcessor(10);
}
pCsr->mode = 0;
}
BOOLEAN CXnNic::NicIsr(PKINTERRUPT, PVOID pvContext)
{
return(((CXnNic *)pvContext)->NicProcessIsr());
}
BOOLEAN CXnNic::NicProcessIsr()
{
Assert(!TestInitFlag(INITF_NIC_STOP));
// Disable interrupts
PNicCsr()->intr_mk = 0;
// Queue the dpc to handle the interrupt
KeInsertQueueDpc(&_dpc, NULL, NULL);
#ifdef XNET_FEATURE_STATS
_NicStats.isrCount += 1;
#endif
return(TRUE);
}
void CXnNic::NicDpc(PKDPC dpc, void * pvContext, void * pvArg1, void * pvArg2)
{
((CXnNic *)pvContext)->NicProcessDpc();
}
void CXnNic::NicProcessDpc()
{
ICHECK(NIC, SDPC);
PNICCSR pCsr = PNicCsr();
STATINC(dpcCount);
while (1)
{
DWORD dwMintr = pCsr->mintr;
DWORD dwIntr = pCsr->intr;
if (dwIntr == 0)
break;
// Process the MII interrupt before acknowledging it to prevent the auto-poll
// of the PHY interfering with reading of the current link state
if (dwIntr & INTR_MINT)
{
NicMiiInterrupt(dwMintr, FALSE);
}
// Acknowledge interrupts (MII interrupts first)
pCsr->mintr = dwMintr;
pCsr->intr = dwIntr;
// We always call these two interrupt handlers no matter what the interrupt
// bits say. They will do nothing if there isn't anything to receive or transmit.
NicRecvInterrupt();
NicXmitInterrupt();
// If a receive frame was missed, tell the Nic to restart RX polling
if (dwIntr & INTR_MISS)
{
STATINC(rxMissedFrames);
pCsr->mode = MODE_RXDM;
}
}
// Re-enable interrupts
pCsr->intr_mk = INTR_ALL;
}
void CXnNic::NicMiiInterrupt(DWORD dwMintr, BOOL fInit)
{
ICHECK(NIC, USER|UDPC|SDPC);
PNICCSR pCsr = PNicCsr();
DWORD dwMiics = pCsr->mii_cs;
DWORD dwLinkState = PhyGetLinkState(!fInit);
if (fInit || (dwLinkState != _dwLinkState))
{
TraceSz3(Warning, "+Ethernet link status: %s %dMbps %s-duplex",
(dwLinkState & XNET_ETHERNET_LINK_ACTIVE) ? "up" : "down",
(dwLinkState & XNET_ETHERNET_LINK_100MBPS) ? 100 :
(dwLinkState & XNET_ETHERNET_LINK_10MBPS) ? 10 : 0,
(dwLinkState & XNET_ETHERNET_LINK_FULL_DUPLEX) ? "full" :
(dwLinkState & XNET_ETHERNET_LINK_HALF_DUPLEX) ? "half" : "?");
// NOTE: When the link was up before, we need to stop both Tx and Rx and then set
// Rx polling frequency and Tx duplex mode according to the link status.
if (!fInit)
{
NicStopXmitRecv();
}
_dwRxPollFreq = (dwLinkState & XNET_ETHERNET_LINK_10MBPS) ? RXPOLL_FREQ_10MPS : RXPOLL_FREQ_100MPS;
if (dwLinkState & XNET_ETHERNET_LINK_FULL_DUPLEX)
pCsr->tx_cntl &= ~TXCNTL_HDEN;
else
pCsr->tx_cntl |= TXCNTL_HDEN;
if (!fInit)
{
NicStartXmitRecv();
}
if (fInit && (dwLinkState & XNET_ETHERNET_LINK_ACTIVE))
{
SetInitFlag(INITF_CONNECTED_BOOT);
}
_dwLinkState = dwLinkState;
}
}
void CXnNic::NicRecvInterrupt()
{
ICHECK(NIC, UDPC|SDPC);
CPacket pkt;
void * pv;
UINT cb;
CEnetHdr * pEnetHdr;
UINT uiFlags;
UINT uiType;
RecvDesc * prd;
DWORD dwFlagsCount;
prd = _prdPtr;
while (1)
{
dwFlagsCount = prd->_dwFlagsCount;
if (dwFlagsCount & RXDESC_OWN)
break;
if ((dwFlagsCount & RXDESC_REND) == 0)
{
STATINC(rxEndOfFrameErrors);
goto nextframe;
}
if (dwFlagsCount & RXDESC_ERR)
{
STATINC_(dwFlagsCount & RXDESC_OFOL, rxOverFlowErrors);
STATINC_(dwFlagsCount & RXDESC_CRC, rxCrcErrors);
STATINC_(dwFlagsCount & RXDESC_LFER, rxLengthErrors);
STATINC_(dwFlagsCount & RXDESC_MAX, rxMaxFrameErrors);
STATINC_(dwFlagsCount & RXDESC_LCOL, rxLateCollisions);
STATINC_(dwFlagsCount & RXDESC_RUNT, rxRunts);
STATINC_(dwFlagsCount & RXDESC_FRAM, rxFramingErrors);
// Accept an error frame if RXDESC_FRAM is the only error bit turned on.
// For all other errors we discard the frame.
if ((dwFlagsCount & (RXDESC_OFOL|RXDESC_CRC|RXDESC_LFER|RXDESC_MAX|RXDESC_LCOL|RXDESC_RUNT|RXDESC_FRAM)) != RXDESC_FRAM)
goto nextframe;
}
// Until NicStart is called we drop all incoming frames because the upper
// layers of the stack are still initializing.
if (!TestInitFlag(INITF_NIC_2))
goto nextframe;
pv = VirAddr(prd->_dwPhyAddr);
cb = (dwFlagsCount & 0xFFFF);
pEnetHdr = (CEnetHdr *)pv;
uiFlags = PKTF_TYPE_ENET;
uiType = pEnetHdr->_wType;
PushPktRecvTags(pEnetHdr->_eaDst.IsBroadcast());
if ((dwFlagsCount & (RXDESC_ERR|RXDESC_FRAM|RXDESC_EXTRA)) == (RXDESC_ERR|RXDESC_FRAM|RXDESC_EXTRA))
{
STATINC(rxExtraByteErrors);
if (cb > 0)
{
cb -= 1;
}
}
if (cb < ENET_DATA_MINSIZE)
{
STATINC(rxMinSizeErrors);
goto nextframepop;
}
STATINC(rxGoodFrames);
if (NTOHS((WORD)uiType) <= ENET_DATA_MAXSIZE)
{
CIeeeHdr * pIeeeHdr = (CIeeeHdr *)pEnetHdr;
BYTE abEnetHdr[sizeof(CEnetAddr) * 2];
if (!pIeeeHdr->IsEnetFrame())
{
TraceSz(pktRecv, "[DISCARD] IEEE frame type not supported");
goto nextframepop;
}
// Convert the link header into a standard CEnetHdr
uiType = pIeeeHdr->_wTypeIeee;
pv = (BYTE *)pv + (sizeof(CIeeeHdr) - sizeof(CEnetHdr));
cb -= (sizeof(CIeeeHdr) - sizeof(CEnetHdr));
memcpy(abEnetHdr, pIeeeHdr, sizeof(abEnetHdr));
memcpy(pv, abEnetHdr, sizeof(abEnetHdr));
pEnetHdr = (CEnetHdr *)pv;
pEnetHdr->_wType = (WORD)uiType;
}
pv = (BYTE *)pv + sizeof(CEnetHdr);
cb -= sizeof(CEnetHdr);
TraceSz4(pktRecv, "[ENET %s %s %04X][%d]",
pEnetHdr->_eaDst.Str(), pEnetHdr->_eaSrc.Str(), NTOHS((WORD)uiType), cb);
if (pEnetHdr->_eaDst.IsBroadcast())
{
uiFlags |= PKTF_RECV_BROADCAST;
}
else if (!pEnetHdr->_eaDst.IsEqual(_ea))
{
#ifdef XNET_FEATURE_XBDM_SERVER
if (!pEnetHdr->_eaDst.IsEqual(_eaClient))
#endif
{
TraceSz(pktWarn, "[DISCARD] Frame should not have been accepted by Nic hardware");
goto nextframepop;
}
}
#ifdef XNET_FEATURE_XBDM_SERVER
if ((uiFlags & PKTF_RECV_BROADCAST) || (pEnetHdr->_eaDst.IsEqual(_eaClient)))
{
if (_pXbdmClient)
{
_pXbdmClient->EnetRecv(uiFlags, pv, cb, uiType);
}
else if ((uiFlags & PKTF_RECV_BROADCAST) == 0)
{
TraceSz(pktRecv, "[DISCARD] Title stack not attached");
}
if ((uiFlags & PKTF_RECV_BROADCAST) == 0)
goto nextframepop;
}
#endif
pkt.Init(uiFlags, pv, cb, NULL);
EnetRecv(&pkt, uiType);
nextframepop:
PopPktRecvTags();
nextframe:
prd->_dwFlagsCount = RXDESC_OWN | (NIC_FRAME_SIZE - NIC_FRAME_ALIGNMENT - 1);
prd = PrdNext(prd);
}
_prdPtr = prd;
}
void CXnNic::NicXmitInterrupt()
{
ICHECK(NIC, UDPC|SDPC);
XmitDesc * pxd;
CPacket * ppkt;
DWORD dwFlagsCount;
while (1)
{
ppkt = _pqXmit.GetHead();
if (ppkt == NULL)
break;
pxd = _pxdBusy;
if (ppkt->TestFlags(PKTF_XMIT_DUALPAGE))
{
Assert((pxd->_dwFlagsCount & TXDESC_TEND) == 0);
pxd = PxdNext(pxd);
}
dwFlagsCount = pxd->_dwFlagsCount;
Assert(dwFlagsCount & TXDESC_TEND);
if (dwFlagsCount & TXDESC_OWN)
break;
_pqXmit.RemoveHead();
_pxdBusy = PxdNext(pxd);
_cxdBusy -= (1 + !!ppkt->TestFlags(PKTF_XMIT_DUALPAGE));
MmLockUnlockBufferPages(ppkt->GetEnetHdr(), sizeof(CEnetHdr) + ppkt->GetCb(), TRUE);
if (dwFlagsCount & TXDESC_ERR)
{
STATINC_(dwFlagsCount & TXDESC_UFLO, txUnderflowErrors);
STATINC_(dwFlagsCount & TXDESC_LCOL, txLateCollisions);
STATINC_(dwFlagsCount & TXDESC_LCAR, txLostCarriers);
STATINC_(dwFlagsCount & TXDESC_DEF, txDefers);
STATINC_(dwFlagsCount & TXDESC_EXDEF, txExcessiveDefers);
STATINC_(dwFlagsCount & TXDESC_RTRY, txRetryErrors);
}
else
{
STATINC(txGoodFrames);
}
#ifdef XNET_FEATURE_XBDM_SERVER
if (ppkt->TestFlags(PKTF_XMIT_XBDMCLIENT))
{
CXbdmPacket * ppktXbdm = (CXbdmPacket *)ppkt;
if (_pXbdmClient)
_pXbdmClient->XmitComplete(ppktXbdm->_pvPkt);
else
TraceSz(Warning, "XBDM client detached with active packets in the Xmit queue");
_pqClient.InsertTail(ppktXbdm);
continue;
}
#endif
ppkt->ClearFlags(PKTF_XMIT_DUALPAGE);
ppkt->Complete(this);
}
if (_fXmitFull && _cxdBusy < _cxdPool)
{
_fXmitFull = FALSE;
#ifdef XNET_FEATURE_XBDM_SERVER
if (_pXbdmClient)
_pXbdmClient->EnetPush();
#endif
EnetPush();
}
}
BOOL CXnNic::NicXmitReady()
{
ICHECK(NIC, UDPC|SDPC);
#ifdef XNET_FEATURE_XBDM_CLIENT
if (_pXbdmServer)
{
return(_pXbdmServer->XmitReady());
}
#endif
BOOL fReady = (_cxdBusy < _cxdPool);
if (!fReady)
{
_fXmitFull = TRUE;
}
return(fReady);
}
void CXnNic::NicXmit(CPacket * ppkt)
{
ICHECK(NIC, UDPC|SDPC);
#ifdef XNET_FEATURE_XBDM_CLIENT
if (_pXbdmServer)
{
_pXbdmServer->Xmit(ppkt, ppkt->GetEnetHdr(), ppkt->GetCb() + sizeof(CEnetHdr));
}
else
#endif
{
Assert(_cxdBusy < _cxdPool);
Assert(ppkt->GetCb() <= ENET_DATA_MAXSIZE);
void * pv = ppkt->GetEnetHdr();
UINT cb = ppkt->GetCb() + sizeof(CEnetHdr);
XmitDesc * pxd = _pxdFree;
XmitDesc * pxd0;
UINT cb0;
TraceSz4(pktXmit, "[ENET dst=%s src=%s %04X][%d]",
((CEnetHdr *)pv)->_eaDst.Str(), ((CEnetHdr *)pv)->_eaSrc.Str(),
NTOHS(((CEnetHdr *)pv)->_wType), ppkt->GetCb());
_pqXmit.InsertTail(ppkt);
MmLockUnlockBufferPages(pv, cb, FALSE);
pxd->_dwPhyAddr = MmGetPhysicalAddress(pv);
cb0 = PAGE_SIZE - (pxd->_dwPhyAddr & (PAGE_SIZE - 1));
if (cb <= cb0)
{
// Entire frame is in a single physical page. Only need one transmit destriptor.
// Notice that the length in the _dwFlagsCount is the actual length minus one.
// That is an odd requirement of the nVidia hardware.
ppkt->ClearFlags(PKTF_XMIT_DUALPAGE);
pxd->_dwFlagsCount = TXDESC_OWN | TXDESC_TEND | (cb - 1);
}
else
{
// The frame straddles a page boundary. Use two transmit descriptors. We set
// up the second descriptor before the first one to prevent the Nic from trying
// to transmit the frame before we are done setting up the two descriptors.
ppkt->SetFlags(PKTF_XMIT_DUALPAGE);
pxd0 = pxd;
pxd = PxdNext(pxd0);
_cxdBusy += 1;
pxd->_dwPhyAddr = MmGetPhysicalAddress((BYTE *)pv + cb0);
pxd->_dwFlagsCount = TXDESC_OWN | TXDESC_TEND | (cb - cb0 - 1);
pxd0->_dwFlagsCount = TXDESC_OWN | (cb0 - 1);
}
_pxdFree = PxdNext(pxd);
_cxdBusy += 1;
if (!TestInitFlag(INITF_NIC_STOP))
{
// Tell the Nic to check the transmit ring
PNICCSR pCsr = PNicCsr();
pCsr->mode = MODE_TXDM;
}
}
}
void CXnNic::NicTimer()
{
ICHECK(NIC, SDPC);
#ifdef XNET_FEATURE_XBDM_CLIENT
if (_pXbdmServer)
return;
#endif
// This function gets called every 200ms from the main timer. Occasionally, the
// transmitter will stall with frames on the queue. If there are any frames queued,
// we set the MODE_TXDM bit on the Nic hardware to tell it to check the queue.
if (_cxdBusy > 0)
{
PNICCSR pCsr = PNicCsr();
pCsr->mode = MODE_TXDM;
}
// In case we miss a receive interrupt, process any received frames now
if ((_prdPtr->_dwFlagsCount & RXDESC_OWN) == 0)
{
NicRecvInterrupt();
}
}
NTSTATUS CXnNic::NicInit(XNetInitParams * pxnip)
{
TCHECK(USER);
NTSTATUS status = BaseInit(pxnip);
if (!NT_SUCCESS(status))
return(status);
#ifdef XNET_FEATURE_XBDM_CLIENT
_pXbdmServer = (CXbdmServer *)KeGetCurrentPrcb()->DmEnetFunc;
if (_pXbdmServer)
{
if (!_pXbdmServer->IsValidServer())
{
TraceSz(Warning, "XBDM.DLL is incompatible with this version of XNET.");
return(NETERR_SYSCALL);
}
BOOL fLinkIsUp = FALSE;
status = _pXbdmServer->InitClient(cfgEnetReceiveQueueLength, cfgEnetTransmitQueueLength,
&_ea, &fLinkIsUp);
if (!NT_SUCCESS(status))
return(status);
SetInitFlag(INITF_NIC);
if (fLinkIsUp)
{
SetInitFlag(INITF_CONNECTED_BOOT);
}
return(NETERR_OK);
}
#elif !defined(XNET_FEATURE_XBDM_SERVER)
// For xnets.lib (secure library), we cannot have XBDM owning the network stack.
// If we find that it is running, we tell it to release the NIC hardware now.
// Note that this code will execute on the retail box, but will have no effect
// because pXbdmServer will be NULL always. This code sequence makes it possible
// to run an XBE linked with xnets.lib on a devkit.
CXbdmServer * pXbdmServer = (CXbdmServer *)KeGetCurrentPrcb()->DmEnetFunc;
if (pXbdmServer && pXbdmServer->IsValidServer())
pXbdmServer->NicStop();
#endif
SetInitFlag(INITF_NIC);
StatInit();
PNICCSR pCsr = PNicCsr();
_ulIntrVector = HalGetInterruptVector(XPCICFG_NIC_IRQ, &_irqlIntr);
KeInitializeDpc(&_dpc, NicDpc, this);
KeInitializeInterrupt(&s_InterruptObject, NicIsr, this, _ulIntrVector,
_irqlIntr, LevelSensitive, TRUE);
NicStopXmitRecv();
// Reset buffer management
pCsr->mode = MODE_RESET_BUFFERS;
KeStallExecutionProcessor(10);
pCsr->mode = 0;
KeStallExecutionProcessor(10);
pCsr->mintr_mk = 0;
pCsr->intr_mk = 0;
pCsr->pm_cntl = 0;
pCsr->swtr_cntl = 0;
pCsr->tx_poll = 0;
pCsr->rx_poll = 0;
pCsr->tx_sta = pCsr->tx_sta;
pCsr->rx_sta = pCsr->rx_sta;
pCsr->mintr = pCsr->mintr;
pCsr->intr = pCsr->intr;
_crdPool = cfgEnetReceiveQueueLength;
if (_crdPool > (PAGE_SIZE/2)/sizeof(RecvDesc))
_crdPool = (PAGE_SIZE/2)/sizeof(RecvDesc);
_cxdPool = cfgEnetTransmitQueueLength + 1;
if (_cxdPool > (PAGE_SIZE/2)/sizeof(XmitDesc))
_cxdPool = (PAGE_SIZE/2)/sizeof(XmitDesc);
XmitDesc * pxd = NULL;
UINT cbDma = PAGE_SIZE + (_crdPool * NIC_FRAME_SIZE);
#ifdef XNET_FEATURE_XBDM_SERVER
// Try allocating the DMA memory in the first 64K if possible under XBDM.
pxd = (XmitDesc *)HalDmaAlloc(cbDma, 64 * 1024);
#endif
if (pxd == NULL)
pxd = (XmitDesc *)HalDmaAlloc(cbDma);
if (pxd == NULL)
{
TraceSz(Warning, "Out of memory allocating DMA receive buffers");
return(NETERR_MEMORY);
}
// Clear the entire first page where the transmit and receive descriptors go
memset(pxd, 0, PAGE_SIZE);
// Compute the offset between virtual and physical memory for these descriptors
_dwPhyOff = MmGetPhysicalAddress(pxd) - (DWORD_PTR)pxd;
// Fill in the transmit descriptor pointers
_pxdFirst = pxd;
_pxdBusy = pxd;
_pxdFree = pxd;
_pxdLast = pxd + _cxdPool - 1;
// Decrement _cxdPool so that we can assume that two transmit descriptors are
// available whenever _cxdBusy < _cxdPool. This makes for an easier check later.
_cxdPool -= 1;
RecvDesc * prd = (RecvDesc *)((BYTE *)pxd + (PAGE_SIZE/2));
_prdFirst = prd;
_prdPtr = prd;
_prdLast = prd + _crdPool - 1;
// Fill in the RecvDesc to point to the frame buffers. Notice that we bias the
// frame pointer by NIC_FRAME_ALIGNMENT (2 bytes). This is because the CEnetHdr
// is 14 bytes long, and we'd like the start of the IP packet to be on a four-byte
// boundary.
DWORD dwPhyAddr = (DWORD_PTR)pxd + _dwPhyOff + PAGE_SIZE + NIC_FRAME_ALIGNMENT;
for (; prd <= _prdLast; ++prd, dwPhyAddr += NIC_FRAME_SIZE)
{
prd->_dwPhyAddr = dwPhyAddr;
prd->_dwFlagsCount = RXDESC_OWN | (NIC_FRAME_SIZE - NIC_FRAME_ALIGNMENT - 1);
}
#ifdef XNET_FEATURE_XBDM_SERVER
// Allocate and enqueue enough CXbdmPacket structures to satisfy the worst case
// where the client has completely filled the transmit queue.
CXbdmPacket * ppktXbdm = (CXbdmPacket *)SysAllocZ((_cxdPool + 1) * sizeof(CXbdmPacket), PTAG_CXbdmPacket);
if (ppktXbdm == NULL)
{
TraceSz(Warning, "Out of memory allocating CXbdmPacket");
return(NETERR_MEMORY);
}
_ppktXbdm = ppktXbdm;
for (int cPkt = _cxdPool + 1; cPkt > 0; --cPkt, ++ppktXbdm)
{
_pqClient.InsertTail(ppktXbdm);
}
#endif
ULONG ulType, ulSize;
status = ExQueryNonVolatileSetting(XC_FACTORY_ETHERNET_ADDR, &ulType, (BYTE *)&_ea, sizeof(CEnetAddr), &ulSize);
if (!NT_SUCCESS(status) || ulSize != sizeof(CEnetAddr))
{
// If we failed to read Ethernet address from non-volatile memory,
// pick a random even address among the first 64 addresses of
// the 00-50-f2 address block. This is so that we can at least boot
// on the manufacturing line and start communicating with the test server.
TraceSz(Warning, "**************************************************************************");
TraceSz2(Warning, "Unable to read Ethernet address from EEPROM (status=%08X,ulSize=%d).", status, ulSize);
TraceSz(Warning, "Run the recovery CD to repair. Continuing with a random Ethernet address.");
TraceSz(Warning, "**************************************************************************");
BYTE bAddr;
Rand(&bAddr, sizeof(bAddr));
_ea._ab[1] = 0x50;
_ea._ab[2] = 0xF2;
_ea._ab[5] = (BYTE)(bAddr & 0x3E);
}
_dwRxPollFreq = RXPOLL_FREQ_100MPS;
#ifdef XNET_FEATURE_XBDM_SERVER
if (_ea._ab[5] & 1)
{
TraceSz(Warning, "***************************************************************************");
TraceSz1(Warning, "Ethernet address of this Development Kit is incorrect (%s).", _ea.Str());
TraceSz(Warning, "The last bit of the address should not be set. Clearing last bit and");
TraceSz(Warning, "continuing. There may be conflicts with other network devices as a result.");
TraceSz(Warning, "***************************************************************************");
_ea._ab[5] &= ~1;
}
_eaClient = _ea; // Title always gets EEPROM address
_ea._ab[5] |= 1; // Debug always gets next address
// Enable unicast reception for the Ethernet address of the title stack
pCsr->uni0 = HWADDR0(_eaClient._ab);
pCsr->uni1 = HWADDR1(_eaClient._ab);
// Enable multicast reception for the Ethernet address of the debug stack
pCsr->mult_mk0 = 0xFFFFFFFF;
pCsr->mult_mk1 = 0xFFFF;
pCsr->mult0 = HWADDR0(_ea._ab);
pCsr->mult1 = HWADDR1(_ea._ab);
#else
// Enable unicast reception for the Ethernet address of the title stack
pCsr->uni0 = HWADDR0(_ea._ab);
pCsr->uni1 = HWADDR1(_ea._ab);
// Disable multicast reception
pCsr->mult_mk0 = 0xFFFFFFFF;
pCsr->mult_mk1 = 0xFFFF;
pCsr->mult0 = 0xFFFFFFFF;
pCsr->mult1 = 0xFFFF;
#endif
// Setup transmitter and receiver
// NOTE: nVidia NIC somehow expects the maximum
// receive buffer size is 1518 instead of 1514.
Assert(NIC_FRAME_SIZE - NIC_FRAME_ALIGNMENT > 1518);
pCsr->rx_cntl_1 = 1518;
pCsr->rx_cntl_0 = RXCNTL_DEFAULT;
pCsr->tx_cntl = TXCNTL_DEFAULT;
// Randomly generate a backoff control timeout (single byte), but don't let it
// be zero because that means no seed (aggressive retry).
BYTE bBackoff;
Rand(&bBackoff, sizeof(bBackoff));
bBackoff += (bBackoff == 0);
pCsr->bkoff_cntl = (BKOFFCNTL_DEFAULT & 0xFFFFFF00) | bBackoff;
pCsr->tx_def = TXDEF_DEFAULT;
pCsr->rx_def = RXDEF_DEFAULT;
pCsr->tx_dadr = PhyAddr(_pxdFirst);
pCsr->rx_dadr = PhyAddr(_prdFirst);
pCsr->dlen = ((_crdPool-1) << 16) | (_cxdPool); // _cxdPool already decremented earlier
pCsr->rx_fifo_wm = RXFIFOWM_DEFAULT;
pCsr->tx_fifo_wm = TXFIFOWM_DEFAULT;
// Enable MII auto-polling interrupt (delay auto-poll enable until after PhyInitialize)
pCsr->mii_cs = MIICS_DEFAULT & ~MIICS_APEN;
pCsr->mii_tm = MIITM_DEFAULT;
KeStallExecutionProcessor(50);
// Initialize the PHY
status = PhyInitialize(FALSE, NULL);
if (!NT_SUCCESS(status))
return(status);
pCsr->mii_cs |= MIICS_APEN;
KeStallExecutionProcessor(50);
NicMiiInterrupt(0, TRUE);
NicStartXmitRecv();
pCsr->mintr = pCsr->mintr;
pCsr->intr = pCsr->intr;
pCsr->mintr_mk = MINTR_MAPI;
pCsr->intr_mk = INTR_ALL;
if (!KeConnectInterrupt(&s_InterruptObject))
{
TraceSz(Warning, "Failed to connect NIC interrupt");
return(STATUS_BIOS_FAILED_TO_CONNECT_INTERRUPT);
}
_HalShutdownReg.NotificationRoutine = (PHAL_SHUTDOWN_NOTIFICATION)HalShutdownNotification;
HalRegisterShutdownNotification(&_HalShutdownReg, TRUE);
SetInitFlag(INITF_NIC_1);
#ifdef XNET_FEATURE_XBDM_SERVER
KeGetCurrentPrcb()->DmEnetFunc = &_XbdmServer;
#endif
return(NETERR_OK);
}
void CXnNic::NicStart()
{
ICHECK(NIC, USER);
BaseStart();
Assert(!TestInitFlag(INITF_NIC_STOP));
#ifdef XNET_FEATURE_XBDM_CLIENT
if (_pXbdmServer)
{
_pXbdmServer->AttachClient(&_XbdmClient);
return;
}
#endif
SetInitFlag(INITF_NIC_2);
}
void CXnNic::NicFlush()
{
TCHECK(UDPC|SDPC);
#ifdef XNET_FEATURE_XBDM_CLIENT
if (_pXbdmServer)
return;
#endif
if (!TestInitFlag(INITF_NIC) || TestInitFlag(INITF_NIC_STOP))
return;
// Wait up to half a second for packets queued for transmit to go
EnetPush();
PNICCSR pCsr = PNicCsr();
for (UINT cTimeout = 10000; cTimeout > 0; --cTimeout)
{
pCsr->mode = MODE_TXDM;
NicXmitInterrupt();
if (_pqXmit.IsEmpty())
break;
KeStallExecutionProcessor(50);
}
}
void CXnNic::NicStop()
{
TCHECK(UDPC|SDPC);
if (TestInitFlag(INITF_NIC) && !TestInitFlag(INITF_NIC_STOP))
{
if (TestInitFlag(INITF_NIC_1))
{
HalRegisterShutdownNotification(&_HalShutdownReg, FALSE);
}
#ifdef XNET_FEATURE_XBDM_CLIENT
if (_pXbdmServer)
{
_pXbdmServer->DetachClient();
}
else
#endif
{
if (s_InterruptObject.Connected)
{
KeDisconnectInterrupt(&s_InterruptObject);
}
KeRemoveQueueDpc(&_dpc);
}
SetInitFlag(INITF_NIC_STOP);
}
BaseStop();
}
void CXnNic::NicTerm()
{
TCHECK(UDPC);
NicStop();
SetInitFlag(INITF_NIC_TERM);
if (TestInitFlag(INITF_NIC))
{
StatTerm();
if (!_pqXmit.IsEmpty())
{
TraceSz1(Warning, "Nic shutdown with %d packet(s) queued for transmit", _pqXmit.Count());
}
while (!_pqXmit.IsEmpty())
{
CPacket * ppkt = _pqXmit.RemoveHead();
MmLockUnlockBufferPages(ppkt->GetEnetHdr(), sizeof(CEnetHdr) + ppkt->GetCb(), TRUE);
Assert(!ppkt->TestFlags(PKTF_XMIT_XBDMCLIENT));
PacketFree(ppkt);
}
if (_pxdFirst)
{
HalDmaFree(_pxdFirst);
}
#ifdef XNET_FEATURE_XBDM_SERVER
if (_ppktXbdm)
{
SysFree(_ppktXbdm);
}
KeGetCurrentPrcb()->DmEnetFunc = NULL;
Assert(_pXbdmClient == NULL);
#endif
Assert(!s_InterruptObject.Connected);
}
BaseTerm();
}
void CXnNic::HalShutdownNotification(HAL_SHUTDOWN_REGISTRATION * pHalShutdownReg)
{
KIRQL kirql = ::KeRaiseIrqlToDpcLevel();
CXnNic * pXnNic = (CXnNic *)((BYTE *)pHalShutdownReg - offsetof(CXnNic, _HalShutdownReg));
((CXnIp *)pXnNic)->SecRegShutdown(FALSE);
pXnNic->NicFlush();
pXnNic->NicStop();
::KeLowerIrql(kirql);
}
// ---------------------------------------------------------------------------------------
// Nic Statistics
// ---------------------------------------------------------------------------------------
#ifdef XNET_FEATURE_STATS
void CXnNic::StatInit()
{
_timerStats.Init((PFNTIMER)StatTimer);
}
void CXnNic::StatTerm()
{
TimerSet(&_timerStats, TIMER_INFINITE);
}
void CXnNic::StatInc(UINT iStat)
{
ICHECK(NIC, UDPC|SDPC);
ULONG * pul = (ULONG *)((BYTE *)&_NicStats + iStat);
*pul += 1;
#ifdef XNET_FEATURE_TRACE
if ( Tag(nicStats)
&& ( Tag(nicStatsAll)
|| (iStat > offsetof(NICSTATS, rxGoodFrames))))
{
if (!_timerStats.IsActive())
{
TimerSetRelative(&_timerStats, 5 * TICKS_PER_SECOND);
}
}
#endif
}
void CXnNic::StatTimer(CTimer * pt)
{
ICHECK(NIC, UDPC|SDPC);
#define DUMPSTAT(n) if (_NicStatsLast.n != _NicStats.n) { TraceSz3(nicStats, "%5d [%5d] %s", _NicStats.n - _NicStatsLast.n, _NicStats.n, #n); }
TraceSz7(nicStats, "[tx %d] [txq %d/%d] [rx %d] [rxq %d] [isr %d] [dpc %d]",
_NicStats.txGoodFrames, _cxdBusy, _cxdPool, _NicStats.rxGoodFrames, _crdPool,
_NicStats.isrCount, _NicStats.dpcCount);
DUMPSTAT(txUnderflowErrors);
DUMPSTAT(txLateCollisions);
DUMPSTAT(txLostCarriers);
DUMPSTAT(txDefers);
DUMPSTAT(txExcessiveDefers);
DUMPSTAT(txRetryErrors);
DUMPSTAT(rxMinSizeErrors);
DUMPSTAT(rxFramingErrors);
DUMPSTAT(rxOverFlowErrors);
DUMPSTAT(rxCrcErrors);
DUMPSTAT(rxLengthErrors);
DUMPSTAT(rxMaxFrameErrors);
DUMPSTAT(rxLateCollisions);
DUMPSTAT(rxRunts);
DUMPSTAT(rxExtraByteErrors);
DUMPSTAT(rxMissedFrames);
DUMPSTAT(rxEndOfFrameErrors);
_NicStatsLast = _NicStats;
}
#endif
// ---------------------------------------------------------------------------------------
// CXbdmClient
// ---------------------------------------------------------------------------------------
#ifdef XNET_FEATURE_XBDM_CLIENT
INLINE CXnNic * CXnNic::CXbdmClient::GetXnNic()
{
return((CXnNic *)((BYTE *)this - offsetof(CXnNic, _XbdmClient)));
}
void CXnNic::CXbdmClient::XmitComplete(void * pvPkt)
{
((CPacket *)pvPkt)->Complete(GetXnNic());
}
void CXnNic::CXbdmClient::EnetRecv(UINT uiFlags, void * pv, UINT cb, UINT uiType)
{
CPacket pkt;
pkt.Init(uiFlags, pv, cb, NULL);
GetXnNic()->PushPktRecvTags(pkt.GetEnetHdr()->_eaDst.IsBroadcast());
GetXnNic()->EnetRecv(&pkt, uiType);
GetXnNic()->PopPktRecvTags();
}
void CXnNic::CXbdmClient::EnetPush()
{
GetXnNic()->EnetPush();
}
DWORD CXnNic::CXbdmClient::GetXnAddr(XNADDRXBDM * pxnaXbdm)
{
XNADDR xnaddr;
DWORD dwFlags = ((CXnIp *)GetXnNic())->IpGetXnAddr(&xnaddr);
memcpy(pxnaXbdm->abEnet, xnaddr.abEnet, sizeof(pxnaXbdm->abEnet));
pxnaXbdm->ina = xnaddr.ina;
return(dwFlags);
}
DWORD CXnNic::NicGetOtherXnAddr(XNADDR * pxna)
{
XNADDRXBDM xnaddrXbdm = { 0 };
DWORD dwFlags = _pXbdmServer ? _pXbdmServer->GetXnAddr(&xnaddrXbdm) : XNET_GET_XNADDR_NONE;
memset(pxna, 0, sizeof(*pxna));
memcpy(pxna->abEnet, xnaddrXbdm.abEnet, sizeof(pxna->abEnet));
pxna->ina = xnaddrXbdm.ina;
return(dwFlags);
}
#endif
// ---------------------------------------------------------------------------------------
// CXbdmServer
// ---------------------------------------------------------------------------------------
#ifdef XNET_FEATURE_XBDM_SERVER
INLINE CXnNic * CXnNic::CXbdmServer::GetXnNic()
{
return((CXnNic *)((BYTE *)this - offsetof(CXnNic, _XbdmServer)));
}
void CXnNic::CXbdmServer::NicStop()
{
Assert(GetXnNic()->_pXbdmClient == NULL);
KIRQL irql = ::KeRaiseIrqlToDpcLevel();
GetXnNic()->NicStop();
KeGetCurrentPrcb()->DmEnetFunc = NULL;
::KeLowerIrql(irql);
}
NTSTATUS CXnNic::CXbdmServer::InitClient(UINT cfgRecvQ, UINT cfgXmitQ, CEnetAddr * pea, BOOL * pfLinkIsUp)
{
Assert(GetXnNic()->_pXbdmClient == NULL);
*pea = GetXnNic()->_eaClient;
*pfLinkIsUp = GetXnNic()->TestInitFlag(INITF_CONNECTED_BOOT);
return(NETERR_OK);
}
void CXnNic::CXbdmServer::AttachClient(CXbdmClient * pXbdmClient)
{
Assert(GetXnNic()->_pXbdmClient == NULL);
KIRQL irql = ::KeRaiseIrqlToDpcLevel();
GetXnNic()->_pXbdmClient = pXbdmClient;
::KeLowerIrql(irql);
}
void CXnNic::CXbdmServer::DetachClient()
{
Assert(KeGetCurrentIrql() == DISPATCH_LEVEL);
CXnNic * pXnNic = GetXnNic();
PNICCSR pCsr = PNicCsr();
UINT cTimeout = 0;
while (!pXnNic->_pqXmit.IsEmpty())
{
pCsr->mode = MODE_TXDM;
pXnNic->NicXmitInterrupt();
KeStallExecutionProcessor(50);
cTimeout++;
AssertSz(cTimeout != 20000, "Taking too long to flush transmit queue");
}
pXnNic->_pXbdmClient = NULL;
}
void CXnNic::CXbdmServer::Xmit(void * pvPkt, void * pv, UINT cb)
{
CXnNic * pXnNic = GetXnNic();
Assert(pXnNic->_pXbdmClient != NULL);
Assert(!pXnNic->_pqClient.IsEmpty());
CXbdmPacket * ppktXbdm = (CXbdmPacket *)pXnNic->_pqClient.RemoveHead();
ppktXbdm->Init(PKTF_XMIT_XBDMCLIENT, (BYTE *)pv + sizeof(CEnetHdr), cb - sizeof(CEnetHdr), NULL);
ppktXbdm->_pvPkt = pvPkt;
pXnNic->NicXmit(ppktXbdm);
}
BOOL CXnNic::CXbdmServer::XmitReady()
{
Assert(GetXnNic()->_pXbdmClient != NULL);
BOOL fReady = GetXnNic()->NicXmitReady();
return(fReady);
}
DWORD CXnNic::CXbdmServer::GetXnAddr(XNADDRXBDM * pxnaXbdm)
{
XNADDR xnaddr;
DWORD dwFlags = ((CXnIp *)GetXnNic())->IpGetXnAddr(&xnaddr);
memcpy(pxnaXbdm->abEnet, xnaddr.abEnet, sizeof(pxnaXbdm->abEnet));
pxnaXbdm->ina = xnaddr.ina;
return(dwFlags);
}
DWORD CXnNic::NicGetOtherXnAddr(XNADDR * pxna)
{
XNADDRXBDM xnaddrXbdm = { 0 };
DWORD dwFlags = _pXbdmClient ? _pXbdmClient->GetXnAddr(&xnaddrXbdm) : XNET_GET_XNADDR_NONE;
memset(pxna, 0, sizeof(*pxna));
memcpy(pxna->abEnet, xnaddrXbdm.abEnet, sizeof(pxna->abEnet));
pxna->ina = xnaddrXbdm.ina;
return(dwFlags);
}
#endif
// ---------------------------------------------------------------------------------------
#endif