AppleDP83816_HW.cpp   [plain text]


/*
 * Copyright (c) 2004 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * The contents of this file constitute Original Code as defined in and
 * are subject to the Apple Public Source License Version 2.0 (the
 * "License").  You may not use this file except in compliance with the
 * License.  Please obtain a copy of the License at
 * http://www.apple.com/publicsource and read it before using this file.
 * 
 * This Original Code and all software distributed under the License are
 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

#include "AppleDP83816.h"

#define CLASS AppleDP83816Ethernet
#define super IOEthernetController

#pragma mark -
#pragma mark ••• Hardware Probe •••
#pragma mark -

//---------------------------------------------------------------------------

bool CLASS::probeHardware( void )
{
    // Cache the initial hardware settings auto-loaded from
    // the EEPROM on hardware reset.

    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();

    // Disable all wakeup sources

    WriteReg(WCSR, 0);

    // Program CFG

    fRegCFG |= CFG_PINT_ACEN;
    fRegCFG &= ~(CFG_EUPHCOMP | CFG_PESEL | CFG_BEM | CFG_PHY_DIS);
    WriteRegFlush(CFG, fRegCFG);

    // Reset internal PHY

    WriteRegFlush(CFG, fRegCFG | CFG_PHY_RST);
    IOSleep(100);
    WriteRegFlush(CFG, fRegCFG);

    // Program descriptor pointers

    WriteReg( RXDP, fRxDescPhysAddr );
    WriteReg( TXDP, fTxDescPhysAddr );

    // MAC Tx and Rx configuration

    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);

    // Clear all statistics counters

    WriteReg(MIBC, MIBC_ACLR);

    // Program receive filters

    setMacAddress();

    fRegRFCR = RFCR_RFEN | RFCR_AAB | RFCR_APM;
    WriteReg(RFCR, fRegRFCR);

    // Program interrupt holdoff (100us units)

    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)
    {
        // Enable Magic Packet support by placing receiver in
        // "silent mode" as described in the DP83816 doc.
        // We assume the receiver has already been disabled.

        WriteReg(RXDP, 0);
        WriteReg(CR, CR_RXE);

        // Setup receive filters

        WriteReg(RFCR, RFCR_RFEN | RFCR_AAB | RFCR_APM | RFCR_AAU);

        // Enable Magic Packet as wakeup source
        
        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;

    // Link the descriptors into a ring.

    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;
    }

    // Wrap the ring - last descriptor points to the first

    fRxDescBase[kRxDescCount - 1].link = fRxDescPhysAddr;

    // Attach cluster mbufs to each receive descriptor.

    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);

        // Total packet length, including the 4-byte FCS?

        rxLength = (rxStatus & kDescBufferSizeMask);

        // Packet must be contained within a single descriptor,
        // and must be a good packet.

        if (((rxStatus & (kDescMore | kDescPacketOK)) == kDescPacketOK) &&
            (rxLength <= kIOEthernetMaxPacketSize + 4))
        {
            pkt = replaceOrCopyPacket(
                  &descPtr->packet, rxLength, &pktReplaced);
            if (pkt)
            {
                if (pktReplaced)
                {
                    // New packet added to ring, update descriptor
                    // with new physical address.

                    OSWriteLittleInt32(&descPtr->bufferPtr, 0, 
                                       MBUF_PADDR(descPtr->packet));
                    assert(descPtr->bufferPtr != 0);
                }

                fNetif->inputPacket(
                        pkt, rxLength,
                        IONetworkInterface::kInputOptionQueuePacket );

                NET_STAT(inputPackets, 1);
            }
            else /* !pkt (mbuf shortage) */
            {
                NET_STAT(inputErrors, 1);
                ETH_STAT(dot3RxExtraEntry.resourceErrors, 1);
                DEBUG_LOG("RX RESOURCE ERROR\n");
            }
        }
        else
        {
            recordRxDescriptorErrors(rxStatus);
        }

        // Update the current descriptor and make it owned by NIC.

        rxStatus = kDescInterrupt | kDescIncludeCRC |
                   (kRxMaxBufferSize & kDescBufferSizeMask);

        OSWriteLittleInt32(&descPtr->cmdStatus, 0, rxStatus);

        // Advance to next descriptor.

        rxIndex  = (rxIndex + 1) & (kRxDescCount - 1);
        descPtr  = &fRxDescBase[rxIndex];
        rxStatus = OSReadLittleInt32(&descPtr->cmdStatus, 0);
    }

    fRxHeadIndex = rxIndex;

    // Push up all packets received in the loop above in one shot.

    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;

    // Link the descriptors into a ring

    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;
    }

    // Wrap the ring - last descriptor points to the first

    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;

    // Check if there are enough descriptors to describe the packet
    // buffers. kTxMaxSegmentCount should be large enough to reduce
    // the need to coalesce mbufs.

    if (TX_RING_FREE(fTxHeadIndex, tailIndex) < kTxMaxSegmentCount)
    {
        IOKernelDebugger::unlock(state);
        return kIOReturnOutputStall;
    }

    // Get the next transmit descriptor owned by the driver.

    descHead = &fTxDescBase[tailIndex];
    descNext = descHead;

    // Use the mbuf cursor to generate a list of physical address and
    // length vectors for the network buffers.

    segCount = fTxMbufCursor->getPhysicalSegmentsWithCoalesce(
                              packet, vectors, kTxMaxSegmentCount);
    if (segCount == 0)
    {
        DEBUG_LOG("TX Cursor returned 0 segments\n");
        goto drop_packet;
    }
    assert(segCount <= kTxMaxSegmentCount);

    // Update the first (head) descriptor.
    // Do not set the OWN bit until the rest of the descriptors are done.

    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++)
    {
        // Write cmdStatus for previous descriptor with MORE bit set.

        OSWriteLittleInt32(&descLast->cmdStatus, 0, cmdStatus | kDescMore);

        // Update current descriptor.

        OSWriteLittleInt32(&descNext->bufferPtr, 0, vectors[seg].location);
        cmdStatus = (vectors[seg].length & kDescBufferSizeMask) | kDescOwn;

        tailIndex = (tailIndex + 1) & (kTxDescCount - 1);
        descLast  = descNext;
        descNext  = &fTxDescBase[tailIndex];
    }

    // Last descriptor must have MORE bit cleared.

    if (++fTxInterruptInterval >= (kTxDescCount/kTxMaxSegmentCount/4))
    {
        cmdStatus |= kDescInterrupt;
        fTxInterruptInterval = 0;
    }

    OSWriteLittleInt32(&descLast->cmdStatus, 0, cmdStatus);
    
    // Set OWN bit on head descriptor after all descriptors following it
    // have been prepared.

    descHead->cmdStatus |= OSSwapHostToLittleConstInt32(kDescOwn);

    // Update Head Descriptor.

    descHead->packet    = packet;
    descHead->descLast  = descLast;
    descHead->descCount = segCount;
    descHead->nextIndex = tailIndex;

    // Update free descriptor count after completing the descriptor chain.
    // The order is important otherwise we may race with interrupt handler.

    fTxTailIndex = tailIndex;
    DEBUG_LOG("TX DESC:%d-%ld (size %d)\n",
              descHead-fTxDescBase, fTxTailIndex, mbuf_pkthdr_len(packet));

    // Enable transmitter in case its in txIdle state.

    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);

        // Examine ownership bit in the last descriptor for this chain.

        txStatus = OSReadLittleInt32(&descPtr->descLast->cmdStatus, 0);
        if (txStatus & kDescOwn)
            break;  // transmit not done yet

        if (txStatus & kDescTxAbnormalMask)
        {
            recordTxDescriptorErrors(txStatus);
        }

        if (descPtr->packet)
        {
            freePacket(descPtr->packet, kDelayFree);
            descPtr->packet = 0;
        }

        // Skip to the start of the next transmit slot.

        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);

    // Prepare descriptor.
    // Must enable descriptor interrupt so that the tx interrupt handler
    // will run after returning from kdp, and clean up the ring.

    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;

    // Wait for KDP buffer send completion before returning.

    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;  // from ms to us

    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))
        {
            // KDP allocates a buffer to hold 1518 bytes.
            // Driver must never overrun this buffer.

            memcpy(pkt_data,
                   mbuf_data(rxDesc->packet),
                   rxLength);

            *pkt_size = rxLength;

            DEBUG_LOG("DEBUG: RX %lu\n", rxLength);
        }

        // Update the current descriptor and make it owned by NIC.

        rxStatus = kDescInterrupt | kDescIncludeCRC |
                   (kRxMaxBufferSize & kDescBufferSizeMask);

        OSWriteLittleInt32(&rxDesc->cmdStatus, 0, rxStatus);

        // Advance to next descriptor.

        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;  // shared or spurious interrupt
    }

    state = IOKernelDebugger::lock(this);        

    while (1)
    {
        status = ReadReg(ISR);  // ack on read
        if ((status & kInterruptSourceMask) == 0)
            break;

        DEBUG_LOG("ISR = 0x%08x\n", status);

        // Receive Interrupt

        if (status & (ISR_RXDESC | ISR_RXOK | ISR_RXERR))
        {
            serviceRxInterrupt();
            ETH_STAT(dot3RxExtraEntry.interrupts, 1);
        }

        // Transmit Interrupt

        if (status & (ISR_TXDESC | ISR_TXURN))
        {
            serviceTxInterrupt();
            ETH_STAT(dot3TxExtraEntry.interrupts, 1);
        }

        // Everything else

        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);
}