#include "BMacEnet.h"
#include "BMacEnetPrivate.h"
#include <IOKit/IORegistryEntry.h>
#include <IOKit/IODeviceTreeSupport.h>
#include <IOKit/platform/AppleMacIODevice.h>
#include <IOKit/assert.h>
#define super IOEthernetController
OSDefineMetaClassAndStructors( BMacEnet, IOEthernetController )
#define PROVIDER_DEV 0
#define PROVIDER_DMA_TX 1
#define PROVIDER_DMA_RX 2
bool BMacEnet::init( OSDictionary* properties )
{
if ( super::init(properties) == false )
return false;
phyId = 0xff;
phyMIIDelay = MII_DEFAULT_DELAY;
sromAddressBits = 6;
enetAddressOffset = 20;
phyStatusPrev = 0;
return true;
}
bool BMacEnet::start(IOService * provider)
{
AppleMacIODevice *nub = OSDynamicCast(AppleMacIODevice, provider);
IOInterruptEventSource *intES;
if (!nub || !super::start(provider))
return false;
transmitQueue = OSDynamicCast(IOGatedOutputQueue, getOutputQueue());
if (!transmitQueue) {
IOLog( "BMacEnet::start: output queue initialization failed\n" );
return false;
}
transmitQueue->retain();
debugQueue = IOPacketQueue::withCapacity((UInt) -1);
if (!debugQueue)
return false;
mbufCursor = IOMbufBigMemoryCursor::withSpecification(NETWORK_BUFSIZE, 2);
if (!mbufCursor) {
IOLog( "BMacEnet::start: IOMbufBigMemoryCursor allocation failure\n" );
return false;
}
for (int i = 0; i < MEMORY_MAP_HEATHROW_INDEX; i++) {
IOMemoryMap * map;
map = provider->mapDeviceMemoryWithIndex(i);
if (!map)
return false;
#ifdef DEBUG_JOE
IOLog("map %d: Phys:%08x Virt:%08x len:%d\n",
i,
(unsigned) map->getPhysicalAddress(),
(unsigned) map->getVirtualAddress(),
(unsigned) map->getLength());
#endif DEBUG_JOE
switch (i) {
case MEMORY_MAP_ENET_INDEX:
ioBaseEnet = (IOPPCAddress) map->getVirtualAddress();
break;
case MEMORY_MAP_TXDMA_INDEX:
ioBaseEnetTxDMA = (IODBDMAChannelRegisters *)
map->getVirtualAddress();
break;
case MEMORY_MAP_RXDMA_INDEX:
ioBaseEnetRxDMA = (IODBDMAChannelRegisters *)
map->getVirtualAddress();
break;
}
maps[i] = map;
}
#ifdef DEBUG_JOE
IOLog("ioBaseEnet: 0x%08x\n", ioBaseEnet);
IOLog("ioBaseEnetTxDMA: 0x%08x\n", ioBaseEnetTxDMA);
IOLog("ioBaseEnetRxDMA: 0x%08x\n", ioBaseEnetRxDMA);
#endif DEBUG_JOE
IOService *heathrow;
if (!(heathrow = OSDynamicCast(IOService,
provider->getParentEntry( gIODTPlane ))))
return false;
OSData * devIDData;
devIDData = OSDynamicCast(OSData, heathrow->getProperty("device-id"));
if (devIDData) {
useUnicastFilter = ( *((UInt32 *) devIDData->getBytesNoCopy()) ==
0x10 );
if (useUnicastFilter)
IOLog("%s: Enabling workaround for broken unicast filter\n",
getName());
}
IOMemoryMap * map = heathrow->mapDeviceMemoryWithIndex(0);
if (map)
{
#ifdef DEBUG_JOE
IOLog("Heathrow: Phys:%08x Virt:%08x len:%d\n",
(unsigned) map->getPhysicalAddress(),
(unsigned) map->getVirtualAddress(),
(unsigned) map->getLength());
#endif DEBUG_JOE
ioBaseHeathrow = (IOPPCAddress) map->getVirtualAddress();
maps[MEMORY_MAP_HEATHROW_INDEX] = map;
}
else {
return false;
}
IOWorkLoop * myWorkLoop = getWorkLoop();
assert(myWorkLoop);
rxIntSrc = IOInterruptEventSource::interruptEventSource(
this,
(IOInterruptEventAction)&BMacEnet::interruptOccurred,
provider,
PROVIDER_DMA_RX );
if ( !rxIntSrc || (myWorkLoop->addEventSource( rxIntSrc ) != kIOReturnSuccess) )
{
IOLog( "BMacEnet::start - rxIntSrc init failure\n" );
return false;
}
intES = IOInterruptEventSource::interruptEventSource(
this,
(IOInterruptEventAction)&BMacEnet::interruptOccurred,
provider,
PROVIDER_DMA_TX );
if ( intES )
{
bool res = (myWorkLoop->addEventSource( intES ) != kIOReturnSuccess );
intES->release();
if ( res )
{
IOLog( "BMacEnet::start - PROVIDER_DMA_TX add failure\n" );
return false;
}
}
else
{
IOLog( "BMacEnet::start - PROVIDER_DMA_TX init failure\n" );
return false;
}
intES = IOInterruptEventSource::interruptEventSource(
this,
(IOInterruptEventAction)&BMacEnet::interruptOccurred,
provider,
PROVIDER_DEV );
if ( intES )
{
bool res = (myWorkLoop->addEventSource( intES ) != kIOReturnSuccess);
intES->release();
if ( res )
{
IOLog( "BMacEnet::start - PROVIDER_DEV add failure\n" );
return false;
}
}
else
{
IOLog( "BMacEnet::start - PROVIDER_DEV init failure\n" );
return false;
}
timerSrc = IOTimerEventSource::timerEventSource(
this,
(IOTimerEventSource::Action)&BMacEnet::timerPopped );
if ( !timerSrc || (myWorkLoop->addEventSource( timerSrc ) != kIOReturnSuccess) )
{
IOLog( "BMacEnet::start - timerSrc init failure\n" );
return false;
}
txDebuggerPkt = allocatePacket( NETWORK_BUFSIZE );
if ( !txDebuggerPkt )
{ IOLog( "BMacEnet::start: Couldn't allocate KDB buffer\n" );
return false;
}
txDebuggerPkt->m_next = 0;
#if 0
#endif
if ( !_resetAndEnable(false) )
{
return false;
}
getHardwareAddress(&myAddress);
if (_allocateMemory() == false)
{
return false;
}
if ( !createMediumTables() )
return false;
if ( !attachInterface((IONetworkInterface **) &networkInterface, false) )
return false;
attachDebuggerClient(&debugger);
networkInterface->registerService();
return true;
}
void BMacEnet::free()
{
UInt i;
if (debugger)
debugger->release();
if (getWorkLoop())
getWorkLoop()->disableAllEventSources();
if (timerSrc)
timerSrc->release();
if (rxIntSrc)
rxIntSrc->release();
if (txDebuggerPkt)
freePacket(txDebuggerPkt);
if (transmitQueue)
transmitQueue->release();
if (debugQueue)
debugQueue->release();
if (networkInterface)
networkInterface->release();
if (mbufCursor)
mbufCursor->release();
if ( mediumDict )
mediumDict->release();
for (i = 0; i < rxMaxCommand; i++)
if (rxMbuf[i]) freePacket(rxMbuf[i]);
for (i = 0; i < txMaxCommand; i++)
if ( txMbuf[i] && (txMbuf[i] != txDebuggerPkt) ) freePacket(txMbuf[i]);
for (i = 0; i < MEMORY_MAP_COUNT; i++)
if (maps[i]) maps[i]->release();
if ( dmaCommandsDesc )
{ dmaCommandsDesc->complete( kIODirectionOutIn );
dmaCommandsDesc->release();
dmaCommandsDesc = 0;
}
if ( dmaCommands )
{
IOFreeContiguous( dmaCommands, dmaCommandsSize );
dmaCommands = 0;
}
if ( workLoop )
{
workLoop->release();
workLoop = 0;
}
super::free();
return;
}
bool BMacEnet::createWorkLoop()
{
workLoop = IOWorkLoop::workLoop();
return ( workLoop != 0 );
}
IOWorkLoop * BMacEnet::getWorkLoop() const
{
return workLoop;
}
void BMacEnet::interruptOccurred( OSObject *me, IOInterruptEventSource *src, int )
{
((BMacEnet*)me)->handleInterrupt( src );
return;
}
void BMacEnet::handleInterrupt( IOInterruptEventSource *src )
{
bool doFlushQueue = false;
bool doService = false;
reserveDebuggerLock();
statReg = ReadBigMacRegister( ioBaseEnet, kSTAT );
if (src == rxIntSrc) {
KERNEL_DEBUG(DBG_BMAC_RXIRQ | DBG_FUNC_START, 0, 0, 0, 0, 0 );
doFlushQueue = _receiveInterruptOccurred();
KERNEL_DEBUG(DBG_BMAC_RXIRQ | DBG_FUNC_END, 0, 0, 0, 0, 0 );
}
else {
txWDInterrupts++;
KERNEL_DEBUG(DBG_BMAC_TXIRQ | DBG_FUNC_START, 0, 0, 0, 0, 0 );
doService = _transmitInterruptOccurred();
KERNEL_DEBUG(DBG_BMAC_TXIRQ | DBG_FUNC_END, 0, 0, 0, 0, 0 );
}
releaseDebuggerLock();
if ( doFlushQueue )
networkInterface->flushInputQueue();
if ( doService && netifEnabled )
transmitQueue->service();
return;
}
UInt32 BMacEnet::outputPacket(struct mbuf * pkt, void * param)
{
u_int32_t i;
UInt32 ret = kIOReturnOutputSuccess;
KERNEL_DEBUG(DBG_BMAC_TXQUEUE | DBG_FUNC_NONE, (int) pkt,
(int) pkt->m_pkthdr.len, 0, 0, 0 );
reserveDebuggerLock();
do
{
assert( pkt && netifEnabled );
#if 0
if ( chipId >= kCHIPID_PaddingtonXmitStreaming )
{
_transmitInterruptOccurred();
}
#endif
i = txCommandTail + 1;
if ( i >= txMaxCommand ) i = 0;
if ( i == txCommandHead )
{
ret = kIOReturnOutputStall;
continue;
}
_transmitPacket(pkt);
}
while ( 0 );
releaseDebuggerLock();
return ret;
}
bool BMacEnet::_resetAndEnable(bool enable)
{
bool ret = true;
ready = false;
if (timerSrc) timerSrc->cancelTimeout();
_disableAdapterInterrupts();
if (getWorkLoop()) getWorkLoop()->disableAllInterrupts();
if (enable)
{
phyId = 0xff;
}
_resetChip();
phyStatusPrev = 0;
setLinkStatus( 0, 0 );
while (enable)
{
if (!_initRxRing() || !_initTxRing())
{
ret = false;
break;
}
if ( phyId != 0xff )
{
miiInitializePHY(phyId);
}
if (_initChip() == false)
{
ret = false;
break;
}
_startChip();
timerSrc->setTimeoutMS(WATCHDOG_TIMER_MS);
if (getWorkLoop()) getWorkLoop()->enableAllInterrupts();
_enableAdapterInterrupts();
ready = true;
_sendDummyPacket();
monitorLinkStatus( true );
break;
}
return ret;
}
void BMacEnet::dropPower()
{ volatile UInt32 *heathrowFCR;
UInt32 fcrValue;
heathrowFCR = (UInt32*)((UInt8*)ioBaseHeathrow + kHeathrowFCR);
fcrValue = OSReadSwapInt32( heathrowFCR, 0 );
fcrValue &= ~(kEnetEnabledBits | kResetEnetCell);
OSWriteSwapInt32( heathrowFCR, 0, fcrValue );
eieio();
return;
}
bool BMacEnet::configureInterface( IONetworkInterface * netif )
{
IONetworkData * nd;
if ( super::configureInterface( netif ) == false )
return false;
nd = netif->getNetworkData( kIONetworkStatsKey );
if ( !nd || !(netStats = (IONetworkStats *) nd->getBuffer()) )
{
IOLog( "BMacEnet::start: invalid network statistics\n" );
return false;
}
return true;
}
IOReturn BMacEnet::enable(IONetworkInterface * netif)
{
if ( netifEnabled )
{
IOLog( "BMacEnet::enable: already enabled\n" );
return kIOReturnSuccess;
}
if ( (ready == false) && !_resetAndEnable(true) )
return kIOReturnIOError;
netifEnabled = true;
transmitQueue->setCapacity(TRANSMIT_QUEUE_SIZE);
transmitQueue->start();
return kIOReturnSuccess;
}
IOReturn BMacEnet::disable(IONetworkInterface * )
{
transmitQueue->stop();
transmitQueue->setCapacity(0);
transmitQueue->flush();
if ( debugEnabled == false )
{
_resetAndEnable( false );
dropPower();
}
netifEnabled = false;
return kIOReturnSuccess;
}
IOReturn BMacEnet::enable(IOKernelDebugger *debugger)
{
if ( (ready == false) && !_resetAndEnable(true) )
{
return kIOReturnIOError;
}
debugEnabled = true;
return kIOReturnSuccess;
}
IOReturn BMacEnet::disable(IOKernelDebugger * )
{
debugEnabled = false;
if ( netifEnabled == false )
{
_resetAndEnable( false );
dropPower();
}
return kIOReturnSuccess;
}
void BMacEnet::timerPopped( OSObject *me, IOTimerEventSource* )
{
((BMacEnet*)me)->handleTimerPop();
return;
}
void BMacEnet::handleTimerPop()
{
u_int32_t dmaStatus;
bool doFlushQueue = false;
bool doService = false;
if ( ready == false )
{
return;
}
reserveDebuggerLock();
dmaStatus = IOGetDBDMAChannelStatus( ioBaseEnetRxDMA );
if ( !(dmaStatus & kdbdmaActive) )
{
#if 0
IOLog( "BMacEnet::handleTimerPop: Timeout check - RxHead = %d RxTail = %d\n",
rxCommandHead, rxCommandTail );
IOLog( "BMacEnet::handleTimerPop: Rx Commands = %08x(p) Rx DMA Ptr = %08x(p)\n", rxDMACommandsPhys, IOGetDBDMACommandPtr(ioBaseEnetRxDMA) );
[self _dumpDesc:(void *)rxDMACommands Size:rxMaxCommand * sizeof(enet_dma_cmd_t)];
#endif
doFlushQueue = _receiveInterruptOccurred();
}
if ( txCommandHead != txCommandTail )
{
if ( txWDInterrupts == 0 )
{
if ( txWDCount++ > 0 )
{
if (_transmitInterruptOccurred() == false)
{
#if 0
IOLog( "BMacEnet::handleTimerPop: Timeout check - TxHead = %d TxTail = %d\n",
txCommandHead, txCommandTail);
#endif
_restartTransmitter();
}
doService = true;
}
}
else
{
txWDInterrupts = 0;
txWDCount = 0;
}
}
else
{
txWDInterrupts = 0;
txWDCount = 0;
}
monitorLinkStatus();
if ( debugTxPoll )
{
debugQueue->flush();
debugTxPoll = false;
doService = true;
}
releaseDebuggerLock();
if ( doFlushQueue )
{
networkInterface->flushInputQueue();
}
if ( doService && netifEnabled )
{
transmitQueue->service();
}
timerSrc->setTimeoutMS( WATCHDOG_TIMER_MS );
return;
}
void BMacEnet::monitorLinkStatus( bool firstPoll )
{
u_int16_t phyStatus;
u_int16_t phyReg;
u_int16_t phyStatusChange;
bool fullDuplex;
bool reportLinkStatus = false;
UInt32 linkSpeed;
UInt32 linkStatus;
if ( phyId == 0xff )
{
phyStatus = ReadBigMacRegister(ioBaseEnet, kXCVRIF);
if ( ( ( phyStatus ^ phyStatusPrev ) & kLinkStatus ) || firstPoll )
{
linkStatus = kIONetworkLinkValid;
linkSpeed = 0;
fullDuplex = false;
reportLinkStatus = true;
if ( ( phyStatus & kLinkStatus ) == 0 )
{
linkSpeed = 10;
linkStatus |= kIONetworkLinkActive;
}
phyStatusPrev = phyStatus;
}
}
else if ( miiReadWord(&phyStatus, MII_STATUS, phyId) == true )
{
phyStatusChange = ( phyStatusPrev ^ phyStatus ) &
( MII_STATUS_LINK_STATUS |
MII_STATUS_NEGOTIATION_COMPLETE);
if ( phyStatusChange || firstPoll )
{
if ( firstPoll )
{
miiWaitForAutoNegotiation( phyId );
miiReadWord(&phyStatus, MII_STATUS, phyId);
miiReadWord(&phyStatus, MII_STATUS, phyId);
}
if ( (phyStatus & MII_STATUS_LINK_STATUS) &&
(firstPoll || (phyStatus & MII_STATUS_NEGOTIATION_COMPLETE)) )
{
if ( (phyType & MII_ST10040_MASK) == MII_ST10040_ID )
{
miiReadWord(&phyReg, MII_ST10040_CHIPST, phyId);
linkSpeed = (phyReg & MII_ST10040_CHIPST_SPEED) ?
100 : 10;
fullDuplex = (phyReg & MII_ST10040_CHIPST_DUPLEX) ?
true : false;
}
else if ( (phyType & MII_DP83843_MASK) == MII_DP83843_ID )
{
miiReadWord(&phyReg, MII_DP83843_PHYSTS, phyId);
linkSpeed = (phyReg & MII_DP83843_PHYSTS_SPEED10) ?
10 : 100;
fullDuplex = (phyReg & MII_DP83843_PHYSTS_DUPLEX) ?
true : false;
}
else
{
linkSpeed = 0;
fullDuplex = false;
}
if ( fullDuplex != isFullDuplex )
{
_setDuplexMode(fullDuplex);
}
linkStatus = kIONetworkLinkActive | kIONetworkLinkValid;
}
else
{
linkStatus = kIONetworkLinkValid;
linkSpeed = 0;
fullDuplex = false;
}
reportLinkStatus = true;
phyStatusPrev = phyStatus;
}
}
if ( reportLinkStatus )
{
IONetworkMedium * medium;
IOMediumType mediumType;
switch ( linkSpeed )
{
case 10:
mediumType = kIOMediumEthernet10BaseT;
mediumType |= (fullDuplex == true) ?
kIOMediumOptionFullDuplex :
kIOMediumOptionHalfDuplex;
break;
case 100:
mediumType = kIOMediumEthernet100BaseTX;
mediumType |= (fullDuplex == true) ?
kIOMediumOptionFullDuplex :
kIOMediumOptionHalfDuplex;
break;
default:
mediumType = kIOMediumEthernetNone;
break;
}
medium = IONetworkMedium::getMediumWithType(mediumDict, mediumType);
setLinkStatus( linkStatus, medium, linkSpeed * 1000000 );
if ( linkStatus & kIONetworkLinkActive )
IOLog( "BMacEnet::monitorLinkStatus: Link up at %ld Mbps - %s Duplex\n",
linkSpeed, (fullDuplex) ? "Full" : "Half" );
else
IOLog( "BMacEnet::monitorLinkStatus: Link down\n" );
}
}
const OSString * BMacEnet::newVendorString() const
{
return OSString::withCString("Apple");
}
const OSString * BMacEnet::newModelString() const
{
return OSString::withCString("BMac");
}
const OSString * BMacEnet::newRevisionString() const
{
return OSString::withCString("");
}
IOReturn BMacEnet::setPromiscuousMode(IOEnetPromiscuousMode mode)
{
u_int16_t rxCFGVal;
reserveDebuggerLock();
rxCFGVal = ReadBigMacRegister(ioBaseEnet, kRXCFG);
WriteBigMacRegister(ioBaseEnet, kRXCFG, rxCFGVal & ~kRxMACEnable );
while( ReadBigMacRegister(ioBaseEnet, kRXCFG) & kRxMACEnable )
;
if (mode == kIOEnetPromiscuousModeOff) {
rxCFGVal &= ~kRxPromiscEnable;
isPromiscuous = false;
}
else {
rxCFGVal |= kRxPromiscEnable;
isPromiscuous = true;
}
WriteBigMacRegister( ioBaseEnet, kRXCFG, rxCFGVal );
releaseDebuggerLock();
return kIOReturnSuccess;
}
IOReturn BMacEnet::setMulticastMode(IOEnetMulticastMode mode)
{
multicastEnabled = (mode == kIOEnetMulticastModeOff) ? false : true;
return kIOReturnSuccess;
}
IOReturn BMacEnet::setMulticastList(IOEthernetAddress *addrs, UInt32 count)
{
reserveDebuggerLock();
_resetHashTableMask();
for (UInt32 i = 0; i < count; i++) {
_addToHashTableMask(addrs->bytes);
addrs++;
}
_updateBMacHashTableMask();
releaseDebuggerLock();
return kIOReturnSuccess;
}
static struct MediumTable
{
UInt32 type;
UInt32 speed;
}
mediumTable[] =
{
{ kIOMediumEthernetNone , 0 },
{ kIOMediumEthernetAuto , 0 },
{ kIOMediumEthernet10BaseT | kIOMediumOptionHalfDuplex, 10 },
{ kIOMediumEthernet10BaseT | kIOMediumOptionFullDuplex, 10 },
{ kIOMediumEthernet100BaseTX | kIOMediumOptionHalfDuplex, 100 },
{ kIOMediumEthernet100BaseTX | kIOMediumOptionFullDuplex, 100 },
};
bool BMacEnet::createMediumTables()
{
IONetworkMedium *medium;
UInt32 i;
mediumDict = OSDictionary::withCapacity( sizeof(mediumTable)/sizeof(mediumTable[0]) );
if ( mediumDict == 0 ) return false;
for ( i=0; i < sizeof(mediumTable)/sizeof(mediumTable[0]); i++ )
{
medium = IONetworkMedium::medium( mediumTable[i].type, mediumTable[i].speed );
if ( medium != 0 )
{
IONetworkMedium::addMedium( mediumDict, medium );
medium->release();
}
}
if ( publishMediumDictionary( mediumDict ) != true )
{
return false;
}
medium = IONetworkMedium::getMediumWithType( mediumDict,
kIOMediumEthernetAuto );
setCurrentMedium( medium );
return true;
}
void BMacEnet::sendPacket( void * pkt, UInt32 pkt_len )
{
_sendPacket(pkt, pkt_len);
}
void BMacEnet::receivePacket( void * pkt,
UInt32 * pkt_len,
UInt32 timeout )
{
_receivePacket(pkt, (UInt *) pkt_len, timeout);
}
IOOutputQueue * BMacEnet::createOutputQueue()
{
return IOGatedOutputQueue::withTarget( this,
getWorkLoop(),
TRANSMIT_QUEUE_SIZE );
}
IOReturn BMacEnet::registerWithPolicyMaker(IOService * policyMaker)
{
#define number_of_power_states 2
static IOPMPowerState ourPowerStates[number_of_power_states] = {
{1,0,0,0,0,0,0,0,0,0,0,0},
{1,IOPMDeviceUsable,IOPMPowerOn,IOPMPowerOn,0,0,0,0,0,0,0,0}
};
currentPowerState = 1;
return policyMaker->registerPowerDriver( this,
ourPowerStates,
number_of_power_states );
}
IOReturn BMacEnet::setPowerState( unsigned long powerStateOrdinal,
IOService * whatDevice )
{
IOReturn ret = IOPMAckImplied;
if ( currentPowerState == powerStateOrdinal )
return IOPMAckImplied;
switch ( powerStateOrdinal )
{
case 0:
kprintf( "BMacEnet::setPowerState: powering off\n" );
currentPowerState = powerStateOrdinal;
break;
case 1:
kprintf( "BMacEnet::setPowerState: powering on\n" );
currentPowerState = powerStateOrdinal;
break;
default:
ret = IOPMNoSuchState;
break;
}
return ret;
}