/*
 * Copyright (c) 1998-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 1.1 (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 <mach/vm_param.h>    // PAGE_SIZE
#include "3C90x.h"

//---------------------------------------------------------------------------
// GetPhysicalFromVirtual

static bool
GetPhysicalFromVirtual( IOBufferMemoryDescriptor * mem,
                        IOVirtualAddress vaddr, IOPhysicalAddress * paddr )
{
    IOByteCount segLength;

    *paddr = mem->getPhysicalSegment(
                     vaddr - (IOVirtualAddress) mem->getBytesNoCopy(),
                     &segLength);

    return ( *paddr != 0 );
}

//---------------------------------------------------------------------------
// allocateDescMemory
//
// Allocate memory for descriptors. Returns true on success.

bool
Apple3Com3C90x::allocateDescMemory( IOBufferMemoryDescriptor ** mem,
                                    IOByteCount                 size )
{
    LOG_DEBUG("%s::%s %ld\n", getName(), __FUNCTION__, size);

    if ( (size == 0) || (size > PAGE_SIZE) || *mem )
    {
        IOLog("%s: unable to allocate descriptor memory\n", getName());
        return false;
    }
    
    *mem = IOBufferMemoryDescriptor::withOptions( kIOMemoryUnshared,
                                                  size, PAGE_SIZE );

    if ( *mem == 0 )
        IOLog("%s: can't allocate %ld bytes of memory\n", getName(), size);

    if ( *mem && (*mem)->prepare() != kIOReturnSuccess)
    {
        (*mem)->release();
        *mem = 0;
    }

    return (*mem != 0);
}

//---------------------------------------------------------------------------
// freeDescMemory
//
// Free descriptor memory allocated through allocateDescMemory().

void
Apple3Com3C90x::freeDescMemory( IOBufferMemoryDescriptor ** mem )
{
    if ( *mem )
    {
        (*mem)->complete();
        (*mem)->release();
        *mem = 0;
    }
}

//---------------------------------------------------------------------------
// allocateMemory
//
// Allocate memory for ring descriptors. This method is called only once by
// init(). Returns true on success.

bool Apple3Com3C90x::allocateMemory()
{
    IOByteCount bytes;

    // Make sure the descriptor sizes lends to proper alignment.

    if ( ( (sizeof(TxDescriptor) % kDescriptorAlignment) != 0 ) || 
         ( (sizeof(RxDescriptor) % kDescriptorAlignment) != 0 ) )
    {
        IOLog("%s: descriptor size not multiple of %d bytes!\n",
              getName(), kDescriptorAlignment);
        return false;
    }

    // Allocate memory for TX ring and initialize it.

    bytes = sizeof(TxDescriptor) * _txRingSize;
    if ( allocateDescMemory( &_txRingMem, bytes ) == false )
        return false;

    _txRing = (TxDescriptor *) _txRingMem->getBytesNoCopy();
    bzero( (void *) _txRing, bytes );

    // Allocate memory for RX ring and initialize it.

    bytes = sizeof(RxDescriptor) * _rxRingSize;
    if ( allocateDescMemory( &_rxRingMem, bytes ) == false )
        return false;

    _rxRing = (RxDescriptor *) _rxRingMem->getBytesNoCopy();
    bzero( (void *) _rxRing, bytes );

    _rxRingInited = false;
    _txRingInited = false;

    return true;
}

//---------------------------------------------------------------------------
// updateDescriptor
//
// Attaches a mbuf to a transmit/receive descriptor. Each descriptor
// can point to multiple memory fragments. However, each packet can
// not span across multiple descriptors. In other words, there is a
// fixed one-to-one mapping between packet and descriptor. Boomerang
// allows a maximum of 63 memory fragments per descriptor. But the
// driver will most likely impose a much smaller limit.

bool
Apple3Com3C90x::updateTxDescriptor( TxDescriptor * desc,
                                    mbuf_t         packet )
{
    UInt32  segmentCount;
    UInt32  checksumDemandMask;
    UInt32  header;

    // Generate the physical segments for the mbuf provided.

    segmentCount = _mbufCursor->getPhysicalSegmentsWithCoalesce(
                   /* mbuf     */ packet,
                   /* segments */ (IOPhysicalSegment *) desc->fragments,
                   /* maxSeg   */ kTxFragmentCount );

    if ( segmentCount == 0 )
    {
        LOG_DEBUG("%s: TX IOMbufMemoryCursor error\n", getName());
        return false;
    }

    // Update the header with the total packet length in bytes.
    // Setting the length in the frame header is no longer required
    // for the newer 3C90xB.

    header = SetBitField( TxFragment, Length, mbuf_pkthdr_len(packet) );

    if ( _hwChecksumEnabled )
    {
        // Set bits to request hardware checksum on this packet.

        getChecksumDemand( packet, kChecksumFamilyTCPIP, &checksumDemandMask );

        header |= ((checksumDemandMask & ( kChecksumIP  |
                                           kChecksumTCP |
                                           kChecksumUDP )) <<
                                           kTxDescHeaderAddIPChecksumShift);        
    }

    OSWriteLittleInt32( &desc->header, 0, header );

    // Set dnLastFrag bit on the last fragment.

    desc->fragments[segmentCount - 1].command |=
          OSSwapHostToLittleConstInt32( kTxFragmentLastMask );

    return true;
}

bool
Apple3Com3C90x::updateRxDescriptor( RxDescriptor * desc,
                                    mbuf_t         packet )
{
    UInt32 segmentCount;

    // Generate the physical segments for the mbuf provided.

    segmentCount = _mbufCursor->getPhysicalSegments(
                   /* mbuf     */ packet,
                   /* segments */ (IOPhysicalSegment *) desc->fragments,
                   /* maxSeg   */ kRxFragmentCount );

    if ( segmentCount == 0 )
    {
        LOG_DEBUG("%s: RX IOMbufMemoryCursor error\n", getName());
        return false;
    }

    // Set dnLastFrag bit on the last fragment.

    desc->fragments[segmentCount - 1].command |=
          OSSwapHostToLittleConstInt32( kRxFragmentLastMask );

    OSSynchronizeIO();

    return true;
}

//---------------------------------------------------------------------------
// initRxRing
//
// Initialize the array of receive descriptors arranged in a ring structure.
// Also allocates cluster mbufs and attach one to each ring element. Returns
// true on success.
//
// Note: This method may be called several times.

bool Apple3Com3C90x::initRxRing()
{
    UInt32            i;
    IOPhysicalAddress phys;

    LOG_DEBUG("%s::%s\n", getName(), __FUNCTION__);

    if ( _rxRingInited ) 
    {
        for ( i = 0; i < _rxRingSize; i++ ) _rxRing[i].status = 0;
        goto ResetPointers;
    }

    for ( i = 0; i < _rxRingSize; i++ )
    {
        // Initialize descriptor, but leave mbuf intact to avoid leaks.

        _rxRing[i].status = 0;
        bzero( &_rxRing[i].fragments, sizeof(_rxRing[i].fragments) );

        // Link current descriptor to next. We skip the last
        // descriptor for now.        

        if ( i < (_rxRingSize - 1) )
        {
            if ( GetPhysicalFromVirtual( _rxRingMem,
                                         (IOVirtualAddress) &_rxRing[i+1],
                                         &phys ) != true )
            {
                return false;
            }

            OSWriteLittleInt32( &_rxRing[i].nextPtr, 0, phys );
        }

        // Attach packet buffer to each descriptor.

        if ( _rxRing[i].drvMbuf == NULL )
        {
            _rxRing[i].drvMbuf = allocatePacket( kRxPacketBufferSize );

            if ( _rxRing[i].drvMbuf == NULL )
            {
                IOLog("%s: initRxRing allocatePacket error\n", getName());
                return false;
            }
        }

        // Link mbuf to descriptor

        if ( updateRxDescriptor( &_rxRing[i], _rxRing[i].drvMbuf) == false )
        {
            IOLog("%s: initRxRing updateDescriptor error", getName());
            return false;
        }

        _rxRing[i].drvNext = &_rxRing[i+1];
    }

    // Wrap the ring by linking the tail to the head.

    if ( GetPhysicalFromVirtual( _rxRingMem,
                                 (IOVirtualAddress) &_rxRing[0],
                                 &phys ) != true )
    {
        return false;
    }
    
    OSWriteLittleInt32( &_rxRing[i-1].nextPtr, 0, phys );

    _rxRing[i-1].drvNext = &_rxRing[0];

ResetPointers:

    // Initialize tail pointer to point to ring element 0.
    // This is where the driver will start looking when a receive
    // interrupt is received.

    _rxRingTail = _rxRing;
    
    _rxRingInited = true;

    return true;
}

//---------------------------------------------------------------------------
// initTxRing
//
// Initialize the array of transmit descriptors arranged in a ring structure.
// Returns true on success.
//
// Note: This method may be called several times.

bool Apple3Com3C90x::initTxRing()
{
    UInt32 i;

    LOG_DEBUG("%s::%s\n", getName(), __FUNCTION__);

    // Free currently linked mbufs.

    for ( i = 0; i < _txRingSize; i++ )
    {
        if ( _txRing[i].drvMbuf )
        {
            freePacket( _txRing[i].drvMbuf );
            _txRing[i].drvMbuf = 0;
        }
    }

    if ( _txRingInited ) goto ResetPointers;

    // Initialize all TX descriptors.

    bzero( (void *) &_txRing[0], sizeof(TxDescriptor) * _txRingSize );
    
    for ( i = 0; i < _txRingSize; i++ )
    {
        // Cache the physical address of the descriptor.
    
        if ( GetPhysicalFromVirtual( _txRingMem,
                                     (IOVirtualAddress) &_txRing[i],
                                     &_txRing[i].drvPhysAddr ) != true )
        {
            return false;
        }

        _txRing[i].drvNext = &_txRing[i+1];

        if ( i > 0 )
            _txRing[i].drvPrevious = &_txRing[i-1];
    }

    // Wrap the ring.

    _txRing[i-1].drvNext   = &_txRing[0];
    _txRing[0].drvPrevious = &_txRing[i-1];

ResetPointers:

    // Clear ring pointers.
    // Head is where new output packets are inserted. Tail is where
    // the transmit interrupt handler looks for packets to retire.

    _txRingHead = _txRing;
    _txRingTail = _txRing;
    _txRingFree = _txRingSize;

    _txRingInited = true;

    return true;
}

//---------------------------------------------------------------------------
// resetAdapter
//
// Reset the transmitter and receiver.

void Apple3Com3C90x::resetAdapter()
{
    LOG_DEBUG("%s::%s\n", getName(), __FUNCTION__);

    // Reset transmitter and receiver.

    sendCommandWait( TxReset );
    sendCommandWait( RxReset );
}

//---------------------------------------------------------------------------
// resetAndEnableAdapter

bool Apple3Com3C90x::resetAndEnableAdapter( bool enableIRQ )
{
    resetAdapter();

    if ( !initRxRing() || !initTxRing() )
    {
        IOLog("%s: initRings failure\n", getName());
        return false;
    }

    initAdapter();

    enableAdapter( enableIRQ );

    return true;
}

//---------------------------------------------------------------------------
// initTransmitterParameters
//
// Initialize the transmitter thresholds and parameters.

void
Apple3Com3C90x::initTransmitterParameters()
{
    // 3C90x. (first generation - Boomerang)
    //
    // When free space in FIFO exceeds a packet, download will begin.
    // If this value is larger than the FIFO size, then downloading
    // is disabled completely.

    setTxFreeThresh( kTxFreeTreshSizePacket );

    // 3C90xB only.
    //
    // Make a download request when the free space in the FIFO exceed
    // the dnBurstThresh value.

    setDnBurstThresh( _dnBurstThresh >> 5 );

    // 3C90xB only.
    //
    // When the transmitter is about to underrun, and the number of
    // used (valid data) bytes in the TX FIFO drops below dnPriorityThresh,
    // issue a priority bus master request.

    setDnPriorityThresh( _dnPriorityThresh >> 5 );

    // 3C90xB only.
    //
    // Reclaim the FIFO space when txReclaimThresh bytes has been
    // transmitted.

    if ( _adapterInfo->type >= kAdapterType3C90xB )
    {
        sendCommand( SetTxReclaimThresh, _txReclaimThresh >> 4 );
    }
}

//---------------------------------------------------------------------------
// initReceiverParameters
//
// Initializes the receiver's thresholds and parameters.

void
Apple3Com3C90x::initReceiverParameters()
{
    // Set RxEarlyThresh to a large value, which in essence disables it.
    // The Boomerang and Cyclone errata recommend this to avoid a receive
    // bus master upload hang.

    sendCommand( SetRxEarlyThresh, 0x1ffc );
    setDMACtrl( getDMACtrl() | kDMACtrlUpRxEarlyEnableMask );

    // 3C90xB only.
    //
    // Determines when bus master upload requests will be made.

    setUpBurstThresh( _upBurstThresh >> 5 );

    // 3C90xB only.
    //
    // Threshold to control when upload engine makes a priority bus master
    // request.

    setUpPriorityThresh( _upPriorityThresh >> 5 );
}

//---------------------------------------------------------------------------
// initAdapter
//
// Initialize the adapter. This function is called during
// driver initialization and also when a timeout occurs.

bool Apple3Com3C90x::initAdapter()
{
    UInt16  reg16;

    LOG_DEBUG("%s::%s\n", getName(), __FUNCTION__);

    // Update Receive list base (UpListPtr).

    sendCommandWait( UpStall );
    // element 0 phys addr
    setUpListPtr( OSSwapLittleToHostInt32(_rxRing[ _rxRingSize - 1 ].nextPtr) );
    sendCommand( UpUnStall );

    // Transmit list base (DnListPtr) is left at 0.

    setDnListPtr( 0 );

    initTransmitterParameters();
    initReceiverParameters();

    // Set station address.

    setStationAddress( &_etherAddress );

    // Configure receive filter.

    sendCommand( SetRxFilter, _rxFilterMask );

    // Clear statistics interrupt by reading them.

    sendCommand( StatsDisable );
    updateStatsInterruptHandler();

    // Turn on upperBytesEnable statistics counter. This should reduce
    // the frequency of statistics interrupts.

    reg16 = getNetworkDiagnostic();
    reg16 |= kNetworkDiagnosticUpperByteEnableMask;
    setNetworkDiagnostic( reg16 );

    // Enable statistics reporting.

    sendCommand( StatsEnable );

#if 0
    // WARNING: Do not enable this block of code! Left as a reminder
    // to investigate the definition of Reset Options register, and
    // why setting certain bits will cause bizarre effects following
    // a warm reboot for newer 3C905C based adapters. The symptoms
    // are bogus PCI vendor/device ID, and weird MAC addresses when
    // read from EEPROM.

    // Disable the overrides in ResetOptions.

    reg16 = getResetOptions();
    reg16 &= ~( kResetOptionsDisableAdvFDMask   |
                kResetOptionsDisableAdv100Mask  |
                kResetOptionsDisableAutoNegMask );
    setResetOptions( reg16 );

    LOG_DEBUG("%s::%s ResetOptions = %04x\n", getName(), __FUNCTION__,
              getResetOptions());
#endif

    return true;
}

//---------------------------------------------------------------------------
// disableAdapterInterrupts
//
// Disables all hardware interrupt sources.

void Apple3Com3C90x::disableAdapterInterrupts()
{
    sendCommand( SetIntrEnable, 0 );
    sendCommand( SetIndEnable,  0 );
    sendCommand( AckIntr, _interruptMask );
    _interruptMask = 0;
}

//---------------------------------------------------------------------------
// enableAdapterInterrupts
//
// Enables all interrupt sources that we are interested in servicing.

void Apple3Com3C90x::enableAdapterInterrupts()
{
    // Set up interrupt mask.
    
    _interruptMask = kCommandStatusDnCompleteMask    |  /* transmit   */
                     kCommandStatusUpCompleteMask    |  /* receive    */
                     kCommandStatusTxCompleteMask    |  /* TX Error   */
                     kCommandStatusUpdateStatsMask   |  /* statistics */  
                     kCommandStatusHostErrorMask     |  /* PCI error  */
                     kCommandStatusInterruptLatchMask;  /* don't care */

    // Enable the indications to appear in IntStatus register.

    sendCommand( SetIndEnable, _interruptMask );

    // Ack any pending stale interrupts.

    sendCommand( AckIntr, _interruptMask );

    // Enable interrupts.

    sendCommand( SetIntrEnable, _interruptMask );
}

//---------------------------------------------------------------------------
// disableLinkInterrupt
//
// Disable further LinkEvent interrupts.

void Apple3Com3C90x::disableLinkEventInterrupt()
{
    _interruptMask &= ~kAckLinkEvent;
    sendCommand( SetIndEnable,  _interruptMask );
    sendCommand( SetIntrEnable, _interruptMask );
}

//---------------------------------------------------------------------------
// enableLinkInterrupt
//
// Re-enable LinkEvent interrupts.

void Apple3Com3C90x::enableLinkEventInterrupt()
{
    _interruptMask |= kAckLinkEvent;
    sendCommand( SetIndEnable,  _interruptMask );
    sendCommand( SetIntrEnable, _interruptMask );
}

//---------------------------------------------------------------------------
// enableTransmitter

void Apple3Com3C90x::enableTransmitter()
{
    LOG_DEBUG("%s: enable transmitter\n", getName());

    sendCommand( TxEnable );
}

//---------------------------------------------------------------------------
// enableReceiver

void Apple3Com3C90x::enableReceiver()
{
    LOG_DEBUG("%s: enable receiver\n", getName());

    sendCommand( RxEnable );
}

//---------------------------------------------------------------------------
// enableAdapter

void Apple3Com3C90x::enableAdapter( bool enableInterrupts )
{
    enableTransmitter();
    enableReceiver();

    if ( enableInterrupts )
    {
        enableAdapterInterrupts();
    }
    
    sendCommand( UpUnStall );
    sendCommand( DnUnStall );
}

//---------------------------------------------------------------------------
// outputPacket
//
// Transmit a single packet mbuf.

UInt32
Apple3Com3C90x::outputPacket( mbuf_t m, void * param )
{
    LOG_TX("%s: transmit [%d]\n", getName(), mbuf_pkthdr_len(m));

    if ( _driverEnableCount == 0 )
    {
        LOG_DEBUG("%s::%s adapter not ready\n", getName(), __FUNCTION__);
        freePacket(m);
        return kIOReturnOutputDropped;
    }

    // Make sure we have space on the descriptor ring for this packet.

    if ( _txRingFree == 0 )
    {
        return kIOReturnOutputStall;
    }

    reserveDebuggerLock();

    // Set up the descriptor.

    if ( _txRingHead->drvMbuf != NULL )
    {
        // This should never occur. Driver error if it did.
        IOLog("%s::%s drvMbuf not NULL\n", getName(), __FUNCTION__);
        freePacket( _txRingHead->drvMbuf );
    }

    if ( updateTxDescriptor( _txRingHead, m ) == false )
    {
        releaseDebuggerLock();
        freePacket(m);
        _netStats->outputErrors++;
        IOLog("%s::%s updateDescriptor error\n", getName(), __FUNCTION__);
        return kIOReturnOutputDropped;
    }

    _txRingHead->drvMbuf = m;
    _txRingHead->nextPtr = 0;  // Stop after processing this entry

    if ( ++_txRingInt >= _txIntThreshold )
    {
        // generate DnComplete interrupt
        _txRingHead->header |=
            OSSwapHostToLittleConstInt32( kTxDescHeaderDnIndicateMask );
        _txRingInt = 0;
    }

    // *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
    // Now do the recommended procedure to attach a descriptor to
    // a descriptor chain/ring.
    //
    // Documented in page 12 of the Boomerang ERS.
    // *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

    // Stall the transmit engine and wait for idle condition.
    
    sendCommandWait( DnStall );

    // link the current descriptor to its previous descriptor.

    OSWriteLittleInt32( &_txRingHead->drvPrevious->nextPtr, 0,
                         _txRingHead->drvPhysAddr );

    // Read DownList and update it if necessary.
#if 0
    if ( getDnListPtr() == 0 )
    {
        setDnListPtr( _txRingHead->drvPhysAddr );
    }
#else
    // Boomerang errata claims that writing to DnListPtr while it is
    // not zero will be ignored. Which means we can replace the above
    // one or two I/O operations with a single write.

    setDnListPtr( _txRingHead->drvPhysAddr );
#endif

    // Unstall the transmit engine.

    sendCommand( DnUnStall );

    // *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
    // Descriptor has been attached.
    // *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

    // Advance the ring index to point to the next free descriptor

    _txRingHead = _txRingHead->drvNext;
    _txRingFree--;

    releaseDebuggerLock();
    
    if ( _txRingInt == 0 )
    {
        _newWDCounters[ kWDInterruptsPending ] += 1;
    }

    return kIOReturnOutputSuccess;
}

//---------------------------------------------------------------------------
// transmitInterruptHandler
//
// Service dnComplete interrupts.

void
Apple3Com3C90x::transmitInterruptHandler()
{
    UInt32  listPtrReg;
    UInt32  retiredCount = 0;

    LOG_INT("%s:%s\n", getName(), __FUNCTION__);

    // Find out which transmit descriptor the engine is currently working on.

    listPtrReg = getDnListPtr();

    while ( _txRingFree < _txRingSize )
    {
        // Make sure the transmit engine has finished with this descriptor
        // before retiring it.

        if ( listPtrReg == _txRingTail->drvPhysAddr )
            break;

#if 0        
        if ( _adapterInfo->type == ADAPTER_TYPE_3C90XB )
        {
            if ( ( _txRingTail->header.bits.dnComplete ) == 0 )
            {
                // 3C90xB indicates that download for this packet is
                // not yet complete.
                IOLog("3C90xB: entry %d not yet downloaded!\n", entry);
            }
        }
#endif

        // Return mbuf to the global pool.

        if ( _txRingTail->drvMbuf != NULL )
        {
            freePacket( _txRingTail->drvMbuf );
            _txRingTail->drvMbuf = NULL;
        }

        // Advance ring tail pointer.

        _txRingTail = _txRingTail->drvNext;
        _txRingFree++;
        retiredCount++;
    }

    if ( retiredCount )
    {
        _transmitQueue->service();

        _netStats->outputPackets += retiredCount;
        _newWDCounters[ kWDInterruptsRetired ] += 1;
    }
}

//---------------------------------------------------------------------------
// receiveInterruptHandler
//
// Service receive interrupts.

void
Apple3Com3C90x::receiveInterruptHandler()
{
    mbuf_t        inputPkt;
    UInt32        pktSize;
    UInt32        status;
    bool          replaced;
    UInt32        pktCount = 0;

    LOG_INT("%s::%s index:%d\n", getName(), __FUNCTION__,
            _rxRingTail - _rxRing);

    for ( status = OSReadLittleInt32( &_rxRingTail->status, 0 );
          status & kRxDescStatusUpCompleteMask;
          status = OSReadLittleInt32( &_rxRingTail->status, 0 ) )
    {
        // Packet passed to inputPacket().

        inputPkt = NULL;

        // Get received packet length (not including FCS).

        pktSize = GetBitField( RxDescStatus, Length, status );

        // Reject bad packets.

        if ( ( status & kRxDescStatusUpErrorMask ) ||
             ( pktSize < (kIOEthernetMinPacketSize - kIOEthernetCRCSize) ) )
        {
            _netStats->inputErrors++;

            if ( status & kRxDescStatusUpOverrunMask )
                _etherStats->dot3RxExtraEntry.overruns++;
            if ( status & kRxDescStatusRuntFrameMask )
                _etherStats->dot3RxExtraEntry.frameTooShorts++;
            if ( status & kRxDescStatusAlignmentErrorMask )
                _etherStats->dot3StatsEntry.alignmentErrors++;
            if ( status & kRxDescStatusCRCErrorMask )
                _etherStats->dot3StatsEntry.fcsErrors++;
            if ( status & kRxDescStatusOversizedFrameMask )
                _etherStats->dot3StatsEntry.frameTooLongs++;
            goto next;
        }

        // Obtain a mbuf to pass up to the networking stack.

        inputPkt = replaceOrCopyPacket( &_rxRingTail->drvMbuf, pktSize,
                                        &replaced );

        if ( inputPkt == 0 )
        {
            _netStats->inputErrors++;
            _etherStats->dot3RxExtraEntry.resourceErrors++;
            goto next;
        }

        // If packet was replaced, we need to recompute the descriptor's
        // phys address/command fields.

        if ( replaced &&
            ( updateRxDescriptor( _rxRingTail,
                                  _rxRingTail->drvMbuf ) == false ) )
        {
            freePacket( _rxRingTail->drvMbuf );  // free the replacement.
            _rxRingTail->drvMbuf = inputPkt;     // get the old packet back.
            inputPkt = 0;                        // pass up nothing.
            _netStats->inputErrors++;
            IOLog("%s: updateDescriptor() error\n", getName());
        }

next:
        // Clear descriptor status.

        _rxRingTail->status = 0;
        OSSynchronizeIO();

        // Advance tail to point to the next descriptor.

        _rxRingTail = _rxRingTail->drvNext;

        if ( inputPkt && _netifEnabled )
        {
            // Record the hardware checksum status bits on the mbuf
            // we are passing up.

            if ( _hwChecksumEnabled )
            {
                UInt32 resultMask;
                UInt32 validMask;

                resultMask = (status & ( kRxDescStatusIPChecksumCheckedMask  |
                                         kRxDescStatusTCPChecksumCheckedMask |
                                         kRxDescStatusUDPChecksumCheckedMask ))
                                      >> kRxDescStatusIPChecksumCheckedShift;

                validMask  = (~status & ( kRxDescStatusIPChecksumErrorMask  |
                                          kRxDescStatusTCPChecksumErrorMask |
                                          kRxDescStatusUDPChecksumErrorMask ))
                                       >> kRxDescStatusIPChecksumErrorShift;
                
                setChecksumResult( inputPkt, kChecksumFamilyTCPIP,
                                   resultMask, validMask );
            }

            // Submit the packet to the interface input queue.

            _netif->inputPacket( inputPkt, pktSize,
                                 IONetworkInterface::kInputOptionQueuePacket );

            _netStats->inputPackets++;

            pktCount++;
        }
    }

    // Unstall engine, in case the card has stalled.

    sendCommand( UpUnStall );

    // Submit all input packets in one shot.

    if ( pktCount )
    {
        _netif->flushInputQueue();
    }
}

//---------------------------------------------------------------------------
// transmitErrorInterruptHandler
//
// Handles various forms of transmitter errors.

void
Apple3Com3C90x::transmitErrorInterruptHandler()
{
    UInt8   txStat;
    UInt32  downListPtr;
    UInt16  intStatus;

    LOG_INT("%s: transmit error interrupt\n", getName());

    txStat      = getTxStatus();
    intStatus   = getCommandStatus();
    downListPtr = getDnListPtr();

    // Acknowledge the txComplete interrupt by writing an arbitrary
    // value to the TxStatus register. This will advance the FIFO.

    setTxStatus( txStat );

    // We need to do different things based on the type of error

    _netStats->outputErrors++;

    if ( txStat & kTxStatusUnderrunMask )
    {
        // Stall the transmitter.

        sendCommandWait( DnStall );

        // Ensure that download and transmission processes are stalled.

        waitForTransmitterIdle();

        // Reset the transmitter.

        sendCommandWait( TxReset );

        _etherStats->dot3TxExtraEntry.underruns++;
        _etherStats->dot3TxExtraEntry.resets++;
    }
    else if ( txStat & kTxStatusJabberMask )
    {
        sendCommandWait( TxReset );

        _etherStats->dot3TxExtraEntry.jabbers++;
        _etherStats->dot3TxExtraEntry.resets++;
    }
    else
    {
        sendCommand( TxEnable );
#if 0
        if ( txStat & kTxStatusMaxCollisionsMask )
        if ( txStat & kTxStatusOverflowMask )
        if ( txStat & kTxStatusReclaimErrorMask )
#endif
        return;
    }

    // Re-enable the transmitter.

    sendCommand( TxEnable );

    // Restore all transmitter parameters following reset.

    initTransmitterParameters();

    // Point DownListPtr at the DPD which experienced the error.

    setDnListPtr( downListPtr );

    // Un-stall the download engine.

    sendCommand( DnUnStall );
}

//---------------------------------------------------------------------------
// hostErrorInterruptHandler
//
// Handles hostError (PCI bus error) interrupts. This is a fatal error
// and a GlobalReset is needed to clear this condition. We assume that
// all interrupt sources in the adapter have been disabled prior to
// calling this method. The hostError interrupt is not cleared until
// a reset is performed.

void
Apple3Com3C90x::hostErrorInterruptHandler()
{
    IOLog("%s: Fatal bus error detected\n", getName());

    // Re-initialize everything.

    if ( resetAndEnable( true ) == false )
    {
        IOLog("%s:%s resetAndEnable error\n", getName(), __FUNCTION__);
    }
}

//---------------------------------------------------------------------------
// linkEventInterruptHandler
//
// Handles 3C90xB LinkEvent interrupts.

void
Apple3Com3C90x::linkEventInterruptHandler()
{
    LOG_INT("%s: linkEventInterruptHandler\n", getName());

    monitorLinkStatus();
}

//---------------------------------------------------------------------------
// updateStatsInterruptHandler
//
// Services updateStats interrupts.

void
Apple3Com3C90x::updateStatsInterruptHandler()
{
    LOG_INT("%s::%s\n", getName(), __FUNCTION__);

    // Fetch the statistics counters from the adapter.
    // This also acks and clears the updateStats interrupt.

    UInt8 lateCollisions   = getLateCollisions();
    UInt8 multiCollisions  = getMultipleCollisions();
    UInt8 singleCollisions = getSingleCollisions();
    UInt8 rxOverruns       = getRxOverruns();
    UInt8 carrierLost      = getCarrierLost();
    UInt8 sqeErrors        = getSqeErrors();

    _netStats->collisions += lateCollisions
                           + multiCollisions
                           + singleCollisions;
 
    _netStats->outputErrors += carrierLost + sqeErrors;

    _etherStats->dot3StatsEntry.deferredTransmissions   += getFramesDeferred();
    _etherStats->dot3StatsEntry.singleCollisionFrames   += singleCollisions;
    _etherStats->dot3StatsEntry.multipleCollisionFrames += multiCollisions;
    _etherStats->dot3StatsEntry.lateCollisions          += lateCollisions;
    _etherStats->dot3StatsEntry.sqeTestErrors           += sqeErrors;
    
    _etherStats->dot3StatsEntry.carrierSenseErrors      += carrierLost;
    _etherStats->dot3StatsEntry.missedFrames            += rxOverruns;

    // Not used, but must read them to clear the interrupt.

    getFramesXmittedOk();
    getBytesXmittedOk();

    getFramesRcvdOk();
    getBytesRcvdOk();    

    // Do this last since the BadSSD register in in a different window
    // from the other statistics counters. Minimize window switches.

    _netStats->inputErrors += rxOverruns + getBadSSD();
}

//---------------------------------------------------------------------------
// interruptHandler
//
// Determine the source of the interrupt and dispatch the appropriate
// interrupt service handler.

void
Apple3Com3C90x::interruptHandler( IOInterruptEventSource * src )
{
    UInt16  status;

    // Make sure the driver is ready to service interrupts.

    if ( _driverEnableCount == 0 )
    {
        LOG_INT("%s: unexpected interrupt\n", getName());
        return;
    }

    reserveDebuggerLock();

    while ( 1 )
    {
        // Read interrupt source status.

        status = getCommandStatus();

        // Qualify the interrupt with the interrupt mask.

        status &= _interruptMask; 

        // Ack all interrupts. 'updateStats' and 'txComplete' interrupts
        // will require special ack sequences.

        sendCommand( AckIntr, status );

        // Nothing else to service, normal exit from this routine.

        if ( status == 0 ) break;

        // Dispatches the appropriate interrupt handlers to service the
        // interrupt(s).

        if ( status & kCommandStatusUpCompleteMask )
        {
            receiveInterruptHandler();
            _etherStats->dot3RxExtraEntry.interrupts++;
        }

        if ( status & kCommandStatusDnCompleteMask )
        {
            transmitInterruptHandler();
            _etherStats->dot3TxExtraEntry.interrupts++;
        }

        // Low frequency interrupts.

        if ( status & ( kCommandStatusUpdateStatsMask |
                        kCommandStatusTxCompleteMask  |
                        kCommandStatusLinkEventMask   |
                        kCommandStatusHostErrorMask ) )
        {
            if ( status & kCommandStatusUpdateStatsMask )
            {
                updateStatsInterruptHandler();
            }
    
            if ( status & kCommandStatusTxCompleteMask )
            {
                transmitErrorInterruptHandler();
            }
    
            if ( status & kCommandStatusLinkEventMask )
            {
                linkEventInterruptHandler();
            }
            
            if ( status & kCommandStatusHostErrorMask )
            {
                hostErrorInterruptHandler();
            }
        }
    }

    releaseDebuggerLock();
}

//---------------------------------------------------------------------------
// Method: setupMulticastHashFilter
//
// Setup the 3C90xB multicast hash filter using the multicast addresses
// provided.

void
Apple3Com3C90x::setupMulticastHashFilter( IOEthernetAddress * addrs,
                                          UInt32              count )
{
    // First clear the multicast hash filter.

    for ( UInt32 i = 0; i < 256; i++ )
    {
        sendCommand( SetHashFilterBit, i | kHashFilterBitOff );
    }

    // Each Ethernet address is hashed to generate an offset to
    // one of the 256 bit slots.

    for ( UInt32 i = 0; i < count; i++ )
    {
        UInt16 bitSlot = hashMulticastAddress( addrs[i].bytes );
        sendCommand( SetHashFilterBit, bitSlot | kHashFilterBitOn );
    }
}
