[plain text]
#include "AppleDP83816.h"
#define CLASS AppleDP83816Ethernet
#define super IOEthernetController
#pragma mark -
#pragma mark ••• Hardware Probe •••
#pragma mark -
bool CLASS::probeHardware( void )
{
fRegSRR = ReadReg(SRR) & SRR_REV_MASK;
fRegCFG = ReadReg(CFG);
DEBUG_LOG("Initial SRR = %lx, CFG = %lx\n", fRegSRR, fRegCFG);
getMacAddress();
return true;
}
void CLASS::getMacAddress( void )
{
union {
UInt8 bytes[4];
UInt32 dword;
} data;
WriteRegFlush( RFCR, RFCR_RFADDR_PM_1_0 );
data.dword = ReadReg( RFDR );
fMacAddress.bytes[0] = data.bytes[0];
fMacAddress.bytes[1] = data.bytes[1];
WriteRegFlush( RFCR, RFCR_RFADDR_PM_3_2 );
data.dword = ReadReg( RFDR );
fMacAddress.bytes[2] = data.bytes[0];
fMacAddress.bytes[3] = data.bytes[1];
WriteRegFlush( RFCR, RFCR_RFADDR_PM_5_4 );
data.dword = ReadReg( RFDR );
fMacAddress.bytes[4] = data.bytes[0];
fMacAddress.bytes[5] = data.bytes[1];
DEBUG_LOG("MAC address = %02x:%02x:%02x:%02x:%02x:%02x\n",
fMacAddress.bytes[0], fMacAddress.bytes[1],
fMacAddress.bytes[2], fMacAddress.bytes[3],
fMacAddress.bytes[4], fMacAddress.bytes[5]);
}
#pragma mark -
#pragma mark ••• Initialization •••
#pragma mark -
void CLASS::setMacAddress( void )
{
union {
UInt8 bytes[4];
UInt32 dword;
} data;
data.dword = 0;
WriteRegFlush( RFCR, RFCR_RFADDR_PM_1_0 );
data.bytes[0] = fMacAddress.bytes[0];
data.bytes[1] = fMacAddress.bytes[1];
WriteRegFlush( RFDR, data.dword );
WriteRegFlush( RFCR, RFCR_RFADDR_PM_3_2 );
data.bytes[0] = fMacAddress.bytes[2];
data.bytes[1] = fMacAddress.bytes[3];
WriteRegFlush( RFDR, data.dword );
WriteRegFlush( RFCR, RFCR_RFADDR_PM_5_4 );
data.bytes[0] = fMacAddress.bytes[4];
data.bytes[1] = fMacAddress.bytes[5];
WriteRegFlush( RFDR, data.dword );
}
bool CLASS::swInit( void )
{
if (!allocateRxMemory())
return false;
if (!allocateTxMemory())
return false;
if (!initRxRing())
return false;
if (!initTxRing())
return false;
return true;
}
void CLASS::swFree( void )
{
releaseRxMemory();
releaseTxMemory();
}
bool CLASS::hwReset( void )
{
int wait;
WriteReg(CR, CR_RST);
for (wait = kHardwareWaitTimeout; wait; wait--)
{
IOSleep(kHardwareWaitValue);
if ((ReadReg(CR) & CR_RST) == 0)
{
DEBUG_LOG("reset complete\n");
break;
}
}
if (!wait)
{
ERROR_LOG("%s: %s TIMEOUT\n", getName(), __FUNCTION__);
return false;
}
return true;
}
void CLASS::hwInit( void )
{
fFullDuplexMode = false;
hwStop();
WriteReg(WCSR, 0);
fRegCFG |= CFG_PINT_ACEN;
fRegCFG &= ~(CFG_EUPHCOMP | CFG_PESEL | CFG_BEM | CFG_PHY_DIS);
WriteRegFlush(CFG, fRegCFG);
WriteRegFlush(CFG, fRegCFG | CFG_PHY_RST);
IOSleep(100);
WriteRegFlush(CFG, fRegCFG);
WriteReg( RXDP, fRxDescPhysAddr );
WriteReg( TXDP, fTxDescPhysAddr );
fRegTXCFG = TXCFG_ATP | TXCFG_MXDMA_256
| ((512/32) << TXCFG_FLTH_SHIFT)
| (( 64/32) << TXCFG_DRTH_SHIFT);
WriteReg(TXCFG, fRegTXCFG);
fRegRXCFG = RXCFG_MXDMA_256 | ((128/8) << RXCFG_DRTH_SHIFT);
WriteReg(RXCFG, fRegRXCFG);
WriteReg(MIBC, MIBC_ACLR);
setMacAddress();
fRegRFCR = RFCR_RFEN | RFCR_AAB | RFCR_APM;
WriteReg(RFCR, fRegRFCR);
WriteReg(IHR, 3);
}
void CLASS::updateMACForDuplexModeChange( void )
{
fRegTXCFG &= ~(TXCFG_CSI | TXCFG_HBI);
fRegRXCFG &= ~RXCFG_ATX;
if (fFullDuplexMode)
{
fRegTXCFG |= (TXCFG_CSI | TXCFG_HBI);
fRegRXCFG |= RXCFG_ATX;
DEBUG_LOG("MAC full-duplex mode\n");
}
WriteReg(TXCFG, fRegTXCFG);
WriteReg(RXCFG, fRegRXCFG);
}
void CLASS::hwStop( void )
{
int wait;
disableHardwareInterrupts();
WriteReg(CR, CR_RXD | CR_TXD);
for (wait = kHardwareWaitTimeout; wait; wait--)
{
IOSleep(kHardwareWaitValue);
if ((ReadReg(CR) & (CR_RXE | CR_TXE)) == 0)
break;
}
if (!wait)
{
ERROR_LOG("%s: %s TIMEOUT\n", getName(), __FUNCTION__);
}
}
void CLASS::hwSetMagicPacketEnable( bool enable )
{
if (enable)
{
WriteReg(RXDP, 0);
WriteReg(CR, CR_RXE);
WriteReg(RFCR, RFCR_RFEN | RFCR_AAB | RFCR_APM | RFCR_AAU);
WriteRegFlush(WCSR, WCSR_WKMAG);
DEBUG_LOG("Magic Packet enabled\n");
}
else
{
WriteReg(RXDP, 0);
WriteReg(CR, CR_RXE);
WriteRegFlush(WCSR, 0);
}
}
#pragma mark -
#pragma mark ••• Receive •••
#pragma mark -
bool CLASS::allocateRxMemory( void )
{
fRxDescMemory = IOBufferMemoryDescriptor::withOptions(
kIOMemoryPhysicallyContiguous,
kRxDescCount * sizeof(RxDesc),
kDescAlignment );
if (!fRxDescMemory)
{
ERROR_LOG("%s: No memory for receive descriptors\n", getName());
return false;
}
if (fRxDescMemory->prepare() != kIOReturnSuccess)
{
fRxDescMemory->release();
fRxDescMemory = 0;
return false;
}
fRxDescPhysAddr = fRxDescMemory->getPhysicalSegment(0, 0);
if (fRxDescPhysAddr == 0)
{
return false;
}
fRxDescBase = (RxDesc *) fRxDescMemory->getBytesNoCopy();
DEBUG_LOG("RX DESC Base V=%p P=0x%lx, count %lu\n",
fRxDescBase, fRxDescPhysAddr, kRxDescCount);
memset(fRxDescBase, 0, kRxDescCount * sizeof(RxDesc));
return true;
}
void CLASS::releaseRxMemory( void )
{
if (fRxDescMemory)
{
for (UInt32 i = 0; i < kRxDescCount; i++)
{
if (fRxDescBase[i].packet)
freePacket(fRxDescBase[i].packet);
}
memset(fRxDescBase, 0, kRxDescCount * sizeof(RxDesc));
fRxDescMemory->complete();
fRxDescMemory->release();
fRxDescMemory = 0;
fRxDescBase = 0;
fRxDescPhysAddr = 0;
}
}
bool CLASS::initRxRing( void )
{
IOPhysicalSegment vector;
UInt segCount;
IOPhysicalAddress physAddr;
RxDesc * descPtr;
UInt32 cmdStatus;
for (int i = kRxDescCount - 1 ;i > 0; i--)
{
physAddr = fRxDescMemory->getPhysicalSegment(sizeof(RxDesc) * i, 0);
if (physAddr == 0)
return false;
fRxDescBase[i-1].link = physAddr;
}
fRxDescBase[kRxDescCount - 1].link = fRxDescPhysAddr;
for (UInt32 i = 0 ;i < kRxDescCount; i++)
{
descPtr = &fRxDescBase[i];
if (descPtr->packet == 0)
descPtr->packet = allocatePacket( kRxMaxBufferSize );
if (descPtr->packet == 0)
return false;
segCount = fRxMbufCursor->getPhysicalSegments(
descPtr->packet, &vector, 1);
if (segCount != 1)
return false;
cmdStatus = kDescInterrupt | kDescIncludeCRC |
(kRxMaxBufferSize & kDescBufferSizeMask);
descPtr->bufferPtr = vector.location;
OSWriteLittleInt32(&descPtr->cmdStatus, 0, cmdStatus);
}
fRxHeadIndex = 0;
return true;
}
#define MBUF_PADDR(m) \
((IOPhysicalAddress) mbuf_data_to_physical(mbuf_data(m)))
void CLASS::serviceRxInterrupt(void)
{
mbuf_t pkt;
UInt32 rxStatus;
UInt32 rxLength;
UInt32 rxIndex = fRxHeadIndex;
RxDesc * descPtr = &fRxDescBase[ rxIndex ];
bool pktReplaced;
rxStatus = OSReadLittleInt32(&descPtr->cmdStatus, 0);
while (rxStatus & kDescOwn)
{
DEBUG_LOG("RX Status = %lx @ index %lu\n", rxStatus, rxIndex);
rxLength = (rxStatus & kDescBufferSizeMask);
if (((rxStatus & (kDescMore | kDescPacketOK)) == kDescPacketOK) &&
(rxLength <= kIOEthernetMaxPacketSize + 4))
{
pkt = replaceOrCopyPacket(
&descPtr->packet, rxLength, &pktReplaced);
if (pkt)
{
if (pktReplaced)
{
OSWriteLittleInt32(&descPtr->bufferPtr, 0,
MBUF_PADDR(descPtr->packet));
assert(descPtr->bufferPtr != 0);
}
fNetif->inputPacket(
pkt, rxLength,
IONetworkInterface::kInputOptionQueuePacket );
NET_STAT(inputPackets, 1);
}
else
{
NET_STAT(inputErrors, 1);
ETH_STAT(dot3RxExtraEntry.resourceErrors, 1);
DEBUG_LOG("RX RESOURCE ERROR\n");
}
}
else
{
recordRxDescriptorErrors(rxStatus);
}
rxStatus = kDescInterrupt | kDescIncludeCRC |
(kRxMaxBufferSize & kDescBufferSizeMask);
OSWriteLittleInt32(&descPtr->cmdStatus, 0, rxStatus);
rxIndex = (rxIndex + 1) & (kRxDescCount - 1);
descPtr = &fRxDescBase[rxIndex];
rxStatus = OSReadLittleInt32(&descPtr->cmdStatus, 0);
}
fRxHeadIndex = rxIndex;
fNetif->flushInputQueue();
}
void CLASS::recordRxDescriptorErrors( UInt32 rxStatus )
{
DEBUG_LOG("RX PACKET ERROR %08lx\n", rxStatus);
NET_STAT(inputErrors, 1);
if (rxStatus & kDescRxOverrun)
ETH_STAT(dot3RxExtraEntry.overruns, 1);
if (rxStatus & kDescTooLong)
ETH_STAT(dot3StatsEntry.frameTooLongs, 1);
if (rxStatus & kDescRuntFrame)
ETH_STAT(dot3RxExtraEntry.frameTooShorts, 1);
if (rxStatus & kDescSymbolError)
ETH_STAT(dot3RxExtraEntry.phyErrors, 1);
if (rxStatus & kDescFCSError)
ETH_STAT(dot3StatsEntry.fcsErrors, 1);
if (rxStatus & kDescRxCollision)
ETH_STAT(dot3RxExtraEntry.collisionErrors, 1);
}
#pragma mark -
#pragma mark ••• Transmit •••
#pragma mark -
bool CLASS::allocateTxMemory( void )
{
fTxDescMemory = IOBufferMemoryDescriptor::withOptions(
kIOMemoryPhysicallyContiguous,
kTxDescCount * sizeof(TxDesc),
kDescAlignment);
if (!fTxDescMemory)
{
ERROR_LOG("%s: No memory for transmit descriptors\n", getName());
return false;
}
if (fTxDescMemory->prepare() != kIOReturnSuccess)
{
fTxDescMemory->release();
fTxDescMemory = 0;
return false;
}
fTxDescPhysAddr = fTxDescMemory->getPhysicalSegment(0, 0);
if (fTxDescPhysAddr == 0)
{
return false;
}
fTxDescBase = (TxDesc *) fTxDescMemory->getBytesNoCopy();
DEBUG_LOG("TX DESC Base V=%p P=0x%lx, count %lu\n",
fTxDescBase, fTxDescPhysAddr, kTxDescCount);
memset(fTxDescBase, 0, kTxDescCount * sizeof(TxDesc));
return true;
}
void CLASS::releaseTxMemory( void )
{
if (fTxDescMemory)
{
for (UInt32 i = 0; i < kTxDescCount; i++)
{
if (fTxDescBase[i].packet)
freePacket(fTxDescBase[i].packet);
}
memset(fTxDescBase, 0, kTxDescCount * sizeof(TxDesc));
fTxDescMemory->complete();
fTxDescMemory->release();
fTxDescMemory = 0;
fTxDescBase = 0;
}
}
bool CLASS::initTxRing( void )
{
IOPhysicalAddress physAddr;
for (int i = kTxDescCount - 1 ;i > 0; i--)
{
physAddr = fTxDescMemory->getPhysicalSegment(sizeof(TxDesc) * i, 0);
if (physAddr == 0)
return false;
fTxDescBase[i-1].link = physAddr;
}
fTxDescBase[kTxDescCount - 1].link = fTxDescPhysAddr;
fTxHeadIndex = 0;
fTxTailIndex = 0;
fTxInterruptInterval = 0;
return true;
}
#define TX_RING_FREE(head, tail) \
(((head) - (tail) - 1) & (kTxDescCount - 1))
#define TX_RING_BUSY(head, tail) \
(((tail) - (head)) & (kTxDescCount - 1))
UInt32 CLASS::outputPacket( mbuf_t packet, void * param )
{
TxDesc * descNext;
TxDesc * descHead;
TxDesc * descLast;
UInt segCount;
UInt32 cmdStatus;
UInt32 tailIndex;
IOPhysicalSegment vectors[ kTxMaxSegmentCount ];
IODebuggerLockState state;
state = IOKernelDebugger::lock(this);
tailIndex = fTxTailIndex;
if (TX_RING_FREE(fTxHeadIndex, tailIndex) < kTxMaxSegmentCount)
{
IOKernelDebugger::unlock(state);
return kIOReturnOutputStall;
}
descHead = &fTxDescBase[tailIndex];
descNext = descHead;
segCount = fTxMbufCursor->getPhysicalSegmentsWithCoalesce(
packet, vectors, kTxMaxSegmentCount);
if (segCount == 0)
{
DEBUG_LOG("TX Cursor returned 0 segments\n");
goto drop_packet;
}
assert(segCount <= kTxMaxSegmentCount);
OSWriteLittleInt32(&descHead->bufferPtr, 0, vectors[0].location);
cmdStatus = (vectors[0].length & kDescBufferSizeMask);
tailIndex = (tailIndex + 1) & (kTxDescCount - 1);
descLast = descHead;
descNext = &fTxDescBase[tailIndex];
for (UInt seg = 1; seg < segCount; seg++)
{
OSWriteLittleInt32(&descLast->cmdStatus, 0, cmdStatus | kDescMore);
OSWriteLittleInt32(&descNext->bufferPtr, 0, vectors[seg].location);
cmdStatus = (vectors[seg].length & kDescBufferSizeMask) | kDescOwn;
tailIndex = (tailIndex + 1) & (kTxDescCount - 1);
descLast = descNext;
descNext = &fTxDescBase[tailIndex];
}
if (++fTxInterruptInterval >= (kTxDescCount/kTxMaxSegmentCount/4))
{
cmdStatus |= kDescInterrupt;
fTxInterruptInterval = 0;
}
OSWriteLittleInt32(&descLast->cmdStatus, 0, cmdStatus);
descHead->cmdStatus |= OSSwapHostToLittleConstInt32(kDescOwn);
descHead->packet = packet;
descHead->descLast = descLast;
descHead->descCount = segCount;
descHead->nextIndex = tailIndex;
fTxTailIndex = tailIndex;
DEBUG_LOG("TX DESC:%d-%ld (size %d)\n",
descHead-fTxDescBase, fTxTailIndex, mbuf_pkthdr_len(packet));
WriteReg(CR, CR_TXE);
IOKernelDebugger::unlock(state);
NET_STAT(outputPackets, 1);
return kIOReturnOutputSuccess;
drop_packet:
IOKernelDebugger::unlock(state);
freePacket(packet);
ETH_STAT(dot3TxExtraEntry.resourceErrors, 1);
return kIOReturnOutputDropped;
}
void CLASS::serviceTxInterrupt( void )
{
TxDesc * descPtr;
UInt32 headIndex = fTxHeadIndex;
UInt32 busyCount = TX_RING_BUSY(headIndex, fTxTailIndex);
UInt32 doneCount = 0;
UInt32 txStatus;
while (doneCount < busyCount)
{
descPtr = &fTxDescBase[ headIndex ];
assert(descPtr->descLast);
assert(descPtr->descCount);
txStatus = OSReadLittleInt32(&descPtr->descLast->cmdStatus, 0);
if (txStatus & kDescOwn)
break;
if (txStatus & kDescTxAbnormalMask)
{
recordTxDescriptorErrors(txStatus);
}
if (descPtr->packet)
{
freePacket(descPtr->packet, kDelayFree);
descPtr->packet = 0;
}
headIndex = descPtr->nextIndex;
doneCount += descPtr->descCount;
}
if (doneCount)
{
fTxHeadIndex = headIndex;
fTransmitQueue->service();
releaseFreePackets();
DEBUG_LOG("TX ISR: retired %lu\n", doneCount);
}
}
void CLASS::recordTxDescriptorErrors( UInt32 txStatus )
{
DEBUG_LOG("TX PACKET ERROR %08lx\n", txStatus);
if (txStatus & kDescTxErrorMask)
NET_STAT(outputErrors, 1);
if (txStatus & (kDescTxAbort | kDescExcessiveDeferral))
ETH_STAT(dot3StatsEntry.internalMacTransmitErrors, 1);
if (txStatus & kDescTxFIFOUnderrun)
ETH_STAT(dot3TxExtraEntry.underruns, 1);
if (txStatus & kDescCarrierSenseLost)
ETH_STAT(dot3StatsEntry.carrierSenseErrors, 1);
if (txStatus & kDescTransmitDeferral)
ETH_STAT(dot3StatsEntry.deferredTransmissions, 1);
if (txStatus & kDescLateCollision)
ETH_STAT(dot3StatsEntry.lateCollisions, 1);
if (txStatus & kDescExcessiveCollisions)
ETH_STAT(dot3StatsEntry.excessiveCollisions, 1);
if (txStatus & kDescTxCollisionCountMask)
{
UInt32 count = (txStatus & kDescTxCollisionCountMask) >>
kDescTxCollisionCountShift;
ETH_STAT(dot3CollEntry.collFrequencies[count], 1);
NET_STAT(collisions, count);
}
}
#pragma mark -
#pragma mark ••• Polled Interface •••
#pragma mark -
void CLASS::waitForFreeTransmitDescriptors( UInt32 freeCount )
{
TxDesc * descPtr;
while (TX_RING_FREE(fTxHeadIndex, fTxTailIndex) < freeCount)
{
descPtr = &fTxDescBase[ fTxHeadIndex ];
while (descPtr->descLast->cmdStatus &
OSSwapHostToLittleConstInt32(kDescOwn))
{
IODelay(20);
}
if (descPtr->packet)
{
fKDPQueue->enqueue(descPtr->packet);
descPtr->packet = 0;
}
fTxHeadIndex = descPtr->nextIndex;
}
}
void CLASS::sendPacket( void * pkt_data, UInt32 pkt_size )
{
TxDesc * descPtr;
UInt32 cmdStatus;
UInt32 tailIndex = fTxTailIndex;
DEBUG_LOG("sendPacket size %ld\n", pkt_size);
if (!pkt_data || pkt_size > kIOEthernetMaxPacketSize) return;
waitForFreeTransmitDescriptors( 1 );
descPtr = &fTxDescBase[ tailIndex ];
memcpy(mbuf_data(fKDPMbuf), pkt_data, pkt_size);
descPtr->bufferPtr = fKDPMbufSeg.location;
cmdStatus = kDescOwn | kDescInterrupt | (pkt_size & kDescBufferSizeMask);
OSWriteLittleInt32(&descPtr->cmdStatus, 0, cmdStatus);
tailIndex = (tailIndex + 1) & (kTxDescCount - 1);
descPtr->packet = 0;
descPtr->descLast = descPtr;
descPtr->descCount = 1;
descPtr->nextIndex = tailIndex;
fTxTailIndex = tailIndex;
WriteRegFlush(CR, CR_TXE);
DEBUG_LOG("sendPacket completion wait...");
while (descPtr->cmdStatus & OSSwapHostToLittleConstInt32(kDescOwn))
{
IODelay(20);
}
DEBUG_LOG("done\n");
}
void CLASS::receivePacket( void * pkt_data, UInt32 * pkt_size,
UInt32 timeout )
{
RxDesc * rxDesc;
UInt32 rxStatus;
UInt32 rxLength;
*pkt_size = 0;
timeout *= 1000;
while (timeout && (*pkt_size == 0))
{
rxDesc = &fRxDescBase[fRxHeadIndex];
rxStatus = OSReadLittleInt32(&rxDesc->cmdStatus, 0);
if ((rxStatus & kDescOwn) == 0)
{
IODelay(20);
timeout -= 20;
continue;
}
DEBUG_LOG("RX Status = %lx @ index %lu\n", rxStatus, fRxHeadIndex);
rxLength = (rxStatus & kDescBufferSizeMask);
if (((rxStatus & (kDescMore | kDescPacketOK)) == kDescPacketOK) &&
(rxLength >= kIOEthernetMinPacketSize) &&
(rxLength <= kIOEthernetMaxPacketSize))
{
memcpy(pkt_data,
mbuf_data(rxDesc->packet),
rxLength);
*pkt_size = rxLength;
DEBUG_LOG("DEBUG: RX %lu\n", rxLength);
}
rxStatus = kDescInterrupt | kDescIncludeCRC |
(kRxMaxBufferSize & kDescBufferSizeMask);
OSWriteLittleInt32(&rxDesc->cmdStatus, 0, rxStatus);
fRxHeadIndex = (fRxHeadIndex + 1) & (kRxDescCount - 1);
}
}
#pragma mark -
#pragma mark ••• Interrupts •••
#pragma mark -
#define kInterruptSourceMask \
( ISR_PHY | \
ISR_TXURN | \
ISR_TXDESC | \
ISR_RXDESC | \
ISR_RXERR | \
ISR_RXOK | \
ISR_DPERR | \
ISR_SSERR | \
ISR_RMABT | \
ISR_RTABT )
void CLASS::enableHardwareInterrupts( void )
{
WriteRegFlush( IMR, kInterruptSourceMask );
WriteRegFlush( IER, IER_IE );
}
void CLASS::disableHardwareInterrupts( void )
{
WriteRegFlush( IER, 0x0 );
}
void CLASS::interruptHandler( OSObject * owner, IOInterruptEventSource *, int )
{
CLASS * me = (CLASS *) owner;
me->interruptOccurred();
}
void CLASS::interruptOccurred( void )
{
UInt32 status;
IODebuggerLockState state;
if (fActivationLevel != kActivationLevelBSD)
{
return; }
state = IOKernelDebugger::lock(this);
while (1)
{
status = ReadReg(ISR); if ((status & kInterruptSourceMask) == 0)
break;
DEBUG_LOG("ISR = 0x%08x\n", status);
if (status & (ISR_RXDESC | ISR_RXOK | ISR_RXERR))
{
serviceRxInterrupt();
ETH_STAT(dot3RxExtraEntry.interrupts, 1);
}
if (status & (ISR_TXDESC | ISR_TXURN))
{
serviceTxInterrupt();
ETH_STAT(dot3TxExtraEntry.interrupts, 1);
}
if (status & (ISR_PHY|ISR_DPERR|ISR_SSERR|ISR_RMABT|ISR_RTABT))
{
if (status & (ISR_DPERR|ISR_SSERR|ISR_RMABT|ISR_RTABT))
{
ERROR_LOG("%s: Fatal PCI Error\n", getName());
}
if (status & ISR_PHY)
{
phyReportLinkStatus();
DEBUG_LOG("Link Change interrupt\n");
}
}
}
IOKernelDebugger::unlock(state);
}
Generated by GNU enscript 1.6.4.