IOKernelDebugger.cpp [plain text]
#include <IOKit/assert.h>
#include <IOKit/IOLib.h>
#include <IOKit/IOMessage.h>
#include <IOKit/IOLocks.h>
#include <IOKit/network/IONetworkController.h>
#include <IOKit/network/IOKernelDebugger.h>
#include <IOKit/IOBSD.h> //for kIOBSDNameKey
#include <libkern/OSAtomic.h>
#include "IONetworkControllerPrivate.h"
#define kIOPrimaryDebugPortKey "IOPrimaryDebugPort"
#define kMatchNameArg "kdp_match_name"
#define kMatchMacArg "kdp_match_mac"
class IOKDP : public IOService
{
OSDeclareDefaultStructors( IOKDP )
public:
virtual IOService *probe(IOService *provider, SInt32 *score);
virtual bool start( IOService * provider );
virtual void stop( IOService * provider );
virtual IOReturn message( UInt32 type,
IOService * provider,
void * argument = 0 );
};
static IOLock * gIOKDPLock = 0;
static IOKDP * gIOKDP = 0;
static UInt32 gDebugBootArg = 0;
class IOKDPGlobals
{
public:
IOKDPGlobals();
~IOKDPGlobals();
inline bool isValid() const;
};
static IOKDPGlobals gIOKDPGlobals;
IOKDPGlobals::IOKDPGlobals()
{
gIOKDPLock = IOLockAlloc();
PE_parse_boot_argn( "debug", &gDebugBootArg, sizeof (gDebugBootArg) );
}
IOKDPGlobals::~IOKDPGlobals()
{
if ( gIOKDPLock )
{
IOLockFree( gIOKDPLock );
gIOKDPLock = 0;
}
}
bool IOKDPGlobals::isValid() const
{
return ( gIOKDPLock );
}
#define super IOService
OSDefineMetaClassAndStructors( IOKDP, IOService )
IOService *IOKDP::probe(IOService *provider, SInt32 *score)
{
char textBuffer[32];
IONetworkInterface *interface = 0;
OSObject *client;
IOService *controller = provider->getProvider();
if(controller)
{
OSIterator *clients = controller->getClientIterator();
while(client = clients->getNextObject())
{
if(interface = OSDynamicCast(IONetworkInterface, client))
break;
}
clients->release();
}
do
{
if(PE_parse_boot_argn( kMatchNameArg, textBuffer, sizeof(textBuffer)))
{
if(!interface) {
return 0;
}
OSString *bsdname = OSDynamicCast(OSString, interface->getProperty(kIOBSDNameKey));
if(!bsdname)
{
return 0;
}
if(bsdname->isEqualTo(textBuffer) == false)
{
return 0;
}
break;
}
if(PE_parse_boot_argn( kMatchMacArg, textBuffer, sizeof(textBuffer)))
{
char ctrMac[13];
if(!controller) {
return 0;
}
OSData * macAddr = OSDynamicCast(OSData, controller->getProperty(kIOMACAddress));
if ( (macAddr == 0) || (macAddr->getLength() != 6) )
{
return 0;
}
for(int i=0; i<12; i++)
textBuffer[i] = textBuffer[i] >= 'a' && textBuffer[i] <= 'z' ? textBuffer[i] - 32 : textBuffer[i];
unsigned char *macData = (unsigned char *)macAddr->getBytesNoCopy();
snprintf(ctrMac, sizeof(ctrMac), "%02X%02X%02X%02X%02X%02X", macData[0], macData[1], macData[2], macData[3], macData[4], macData[5]);
if(strncmp(ctrMac, textBuffer, 12))
{
return 0;
}
break;
}
OSBoolean *pdp = OSDynamicCast(OSBoolean, provider->getProperty(kIOPrimaryDebugPortKey));
if(!pdp || pdp->isFalse())
{
return 0;
}
break;
}while(false);
IOService *ret = super::probe(provider, score);
return ret;
}
bool IOKDP::start( IOService * provider )
{
bool ret = false;
if ( super::start(provider) == false )
return false;
if ( gIOKDPGlobals.isValid() == false )
return false;
IOLockLock( gIOKDPLock );
do {
if ( gIOKDP )
break;
if ( provider->open(this) == false )
break;
gIOKDP = this;
ret = true;
}
while ( false );
IOLockUnlock( gIOKDPLock );
if ( ret ) registerService();
return ret;
}
void IOKDP::stop( IOService * provider )
{
provider->close(this);
IOLockLock( gIOKDPLock );
if ( gIOKDP == this ) gIOKDP = 0;
IOLockUnlock( gIOKDPLock );
super::stop(provider);
}
IOReturn IOKDP::message( UInt32 type,
IOService * provider,
void * argument )
{
if ( type == kIOMessageServiceIsTerminated )
{
provider->close(this);
}
return kIOReturnSuccess;
}
extern "C" {
typedef void (*kdp_send_t)( void * pkt, UInt pkt_len );
typedef void (*kdp_receive_t)( void * pkt, UInt * pkt_len, UInt timeout );
void kdp_register_send_receive( kdp_send_t send, kdp_receive_t receive );
void kdp_unregister_send_receive( kdp_send_t send, kdp_receive_t receive );
void kdp_set_interface(void *);
}
#undef super
#define super IOService
OSDefineMetaClassAndStructors( IOKernelDebugger, IOService )
OSMetaClassDefineReservedUnused( IOKernelDebugger, 0 );
OSMetaClassDefineReservedUnused( IOKernelDebugger, 1 );
OSMetaClassDefineReservedUnused( IOKernelDebugger, 2 );
OSMetaClassDefineReservedUnused( IOKernelDebugger, 3 );
IOService * gIODebuggerDevice = 0;
IODebuggerTxHandler gIODebuggerTxHandler = 0;
IODebuggerRxHandler gIODebuggerRxHandler = 0;
UInt32 gIODebuggerTxBytes = 0;
UInt32 gIODebuggerRxBytes = 0;
SInt32 gIODebuggerSemaphore = 0;
UInt32 gIODebuggerFlag = 0;
enum {
kIODebuggerFlagRegistered = 0x01,
kIODebuggerFlagWarnNullHandler = 0x02
};
#define _enableDebuggerThreadCall _reserved->enableDebuggerThreadCall
#define _disableDebuggerThreadCall _reserved->disableDebuggerThreadCall
#define _state _reserved->stateVars[0]
#define _activationChangeThreadCall _reserved->activationChangeThreadCall
#define _interfaceNotifier _reserved->interfaceNotifier
#define EARLY_DEBUG_SUPPORT (gDebugBootArg != 0)
static void handleActivationChange( IOKernelDebugger * debugger,
void * change );
bool IOKernelDebugger::interfacePublished( void * target, void *param, IOService * service )
{
IOService *debugger = (IOService *)target;
if(debugger && service && debugger->getProvider() == service->getProvider())
{
debugger->registerService();
}
return true;
}
void IOKernelDebugger::kdpReceiveDispatcher( void * buffer,
UInt32 * length,
UInt32 timeout )
{
*length = 0;
if ( gIODebuggerSemaphore ) return;
(*gIODebuggerRxHandler)( gIODebuggerDevice, buffer, length, timeout );
gIODebuggerRxBytes += *length;
}
void IOKernelDebugger::kdpTransmitDispatcher( void * buffer, UInt32 length )
{
if ( gIODebuggerSemaphore ) return;
(*gIODebuggerTxHandler)( gIODebuggerDevice, buffer, length );
gIODebuggerTxBytes += length;
}
void IOKernelDebugger::nullTxHandler( IOService * target,
void * buffer,
UInt32 length )
{
}
void IOKernelDebugger::nullRxHandler( IOService * target,
void * buffer,
UInt32 * length,
UInt32 timeout )
{
if ( gIODebuggerFlag & kIODebuggerFlagWarnNullHandler )
{
IOLog("IOKernelDebugger::%s no debugger device\n", __FUNCTION__);
gIODebuggerFlag &= ~kIODebuggerFlagWarnNullHandler;
}
}
IODebuggerLockState IOKernelDebugger::lock( IOService * object )
{
if ( gIODebuggerDevice == object )
{
OSIncrementAtomic( &gIODebuggerSemaphore );
return kIODebuggerLockTaken;
}
return (IODebuggerLockState) 0;
}
void IOKernelDebugger::unlock( IODebuggerLockState state )
{
if ( state & kIODebuggerLockTaken )
OSDecrementAtomic( &gIODebuggerSemaphore );
}
bool IOKernelDebugger::init( IOService * target,
IODebuggerTxHandler txHandler,
IODebuggerRxHandler rxHandler )
{
if ( ( super::init() == false ) ||
( OSDynamicCast(IOService, target) == 0 ) ||
( txHandler == 0 ) ||
( rxHandler == 0 ) )
{
return false;
}
_reserved = IONew( ExpansionData, 1 );
if ( _reserved == 0 )
{
return false;
}
_enableDebuggerThreadCall = thread_call_allocate(
(thread_call_func_t) pmEnableDebugger,
(thread_call_param_t) this );
_disableDebuggerThreadCall = thread_call_allocate(
(thread_call_func_t) pmDisableDebugger,
(thread_call_param_t) this );
_activationChangeThreadCall = thread_call_allocate(
(thread_call_func_t) handleActivationChange,
(thread_call_param_t) this );
char textBuffer[64];
if(PE_parse_boot_argn( kMatchNameArg, textBuffer, sizeof(textBuffer)))
{
_interfaceNotifier = addNotification(
gIOPublishNotification,
serviceMatching("IONetworkInterface"),
interfacePublished,
this );
}
else _interfaceNotifier = 0;
if ( !_enableDebuggerThreadCall || !_disableDebuggerThreadCall ||
!_activationChangeThreadCall )
{
return false;
}
_target = target;
_txHandler = txHandler;
_rxHandler = rxHandler;
_state = 0;
return true;
}
IOKernelDebugger * IOKernelDebugger::debugger( IOService * target,
IODebuggerTxHandler txHandler,
IODebuggerRxHandler rxHandler )
{
IOKernelDebugger * debugger = new IOKernelDebugger;
if (debugger && (debugger->init( target, txHandler, rxHandler ) == false))
{
debugger->release();
return 0;
}
IOService * device = target->getProvider(); if ( device && device->getProperty( "built-in" )) {
OSObject *locationProperty = device->copyProperty("location");
OSData *locationAsData = OSDynamicCast(OSData, locationProperty);
if(!locationAsData || (strcmp( (char *)locationAsData->getBytesNoCopy(), "1")== 0) ) debugger->setProperty( kIOPrimaryDebugPortKey, true );
else
debugger->setProperty( kIOPrimaryDebugPortKey, false );
if(locationProperty)
locationProperty->release();
}
return debugger;
}
void IOKernelDebugger::registerHandler( IOService * target,
IODebuggerTxHandler txHandler,
IODebuggerRxHandler rxHandler )
{
bool doRegister;
assert( ( target == gIODebuggerDevice ) ||
( target == 0 ) ||
( gIODebuggerDevice == 0 ) );
doRegister = ( target && ( txHandler != 0 ) && ( rxHandler != 0 ) );
if ( !doRegister && ( gIODebuggerFlag & kIODebuggerFlagRegistered ) )
{
kdp_unregister_send_receive( (kdp_send_t) kdpTransmitDispatcher,
(kdp_receive_t) kdpReceiveDispatcher );
gIODebuggerFlag &= ~kIODebuggerFlagRegistered;
}
if ( txHandler == 0 ) txHandler = &IOKernelDebugger::nullTxHandler;
if ( rxHandler == 0 ) rxHandler = &IOKernelDebugger::nullRxHandler;
OSIncrementAtomic( &gIODebuggerSemaphore );
gIODebuggerDevice = target;
gIODebuggerTxHandler = txHandler;
gIODebuggerRxHandler = rxHandler;
gIODebuggerFlag |= kIODebuggerFlagWarnNullHandler;
OSDecrementAtomic( &gIODebuggerSemaphore );
if ( doRegister && (( gIODebuggerFlag & kIODebuggerFlagRegistered ) == 0) )
{
IONetworkInterface *interface = 0;
OSIterator *clients = target->getClientIterator();
while(OSObject *client = clients->getNextObject())
{
if(interface = OSDynamicCast(IONetworkInterface, client))
break;
}
clients->release();
kdp_set_interface(interface);
kdp_register_send_receive( (kdp_send_t) kdpTransmitDispatcher,
(kdp_receive_t) kdpReceiveDispatcher );
gIODebuggerFlag |= kIODebuggerFlagRegistered;
publishResource("kdp");
}
}
enum {
kEventClientOpen = 0x0001,
kEventTargetUsable = 0x0002,
kEventDebuggerActive = 0x0004,
kTargetIsEnabled = 0x0100,
kTargetWasEnabled = 0x0200,
};
static void enableTarget( IOKernelDebugger * self,
IOService * target,
UInt32 * state,
UInt32 event )
{
IONetworkController * ctr = OSDynamicCast( IONetworkController, target );
#define kReadyMask ( kEventClientOpen | \
kEventTargetUsable | \
kEventDebuggerActive )
*state |= event;
*state &= ~kTargetWasEnabled;
if ( ( *state & kReadyMask ) == kReadyMask )
{
if ( !ctr || ( *state & kTargetIsEnabled ) ||
ctr->doEnable( self ) == kIOReturnSuccess )
{
if ( ( *state & kTargetIsEnabled ) == 0 )
*state |= kTargetWasEnabled;
*state |= kTargetIsEnabled;
}
}
}
static void disableTarget( IOKernelDebugger * self,
IOService * target,
UInt32 * state,
UInt32 event )
{
IONetworkController * ctr = OSDynamicCast( IONetworkController, target );
UInt32 on = *state & kTargetIsEnabled;
*state &= ~( event | kTargetIsEnabled | kTargetWasEnabled );
if ( ctr && on ) ctr->doDisable( self );
}
bool IOKernelDebugger::handleOpen( IOService * forClient,
IOOptionBits options,
void * arg )
{
bool ret = false;
do {
if ( _client || !_target ) break;
registerHandler( _target );
if ( _target->open( this ) == false )
break;
if ( EARLY_DEBUG_SUPPORT )
_state |= kEventDebuggerActive;
if ( _target->registerInterestedDriver(this) &
( kIOPMDeviceUsable | kIOPMNotPowerManaged ) )
_state |= kEventTargetUsable;
enableTarget( this, _target, &_state, kEventClientOpen );
if ( _state & kTargetWasEnabled )
{
IOLog("%s: registering debugger\n", getName());
registerHandler( _target, _txHandler, _rxHandler );
}
_client = forClient;
ret = true;
}
while (0);
if ( ret == false )
{
registerHandler( 0 );
_target->close( this );
}
return ret;
}
void IOKernelDebugger::handleClose( IOService * forClient,
IOOptionBits options )
{
if ( _target && _client && ( _client == forClient ) )
{
registerHandler( 0 );
disableTarget( this, _target, &_state, kEventClientOpen );
_target->deRegisterInterestedDriver( this );
_client = 0;
_target->close( this );
}
}
bool IOKernelDebugger::handleIsOpen( const IOService * forClient ) const
{
if ( forClient == 0 )
return ( forClient != _client );
else
return ( forClient == _client );
}
void IOKernelDebugger::free()
{
if ( _reserved )
{
if ( _enableDebuggerThreadCall )
thread_call_free( _enableDebuggerThreadCall );
if ( _disableDebuggerThreadCall )
thread_call_free( _disableDebuggerThreadCall );
if ( _activationChangeThreadCall )
thread_call_free( _activationChangeThreadCall );
if ( _interfaceNotifier )
_interfaceNotifier->remove();
IODelete( _reserved, ExpansionData, 1 );
_reserved = 0;
}
super::free();
}
#define PM_SECS(x) ((x) * 1000 * 1000)
IOReturn
IOKernelDebugger::powerStateWillChangeTo( IOPMPowerFlags flags,
unsigned long stateNumber,
IOService * policyMaker )
{
IOReturn ret = IOPMAckImplied;
if ( ( flags & IOPMDeviceUsable ) == 0 )
{
retain();
if ( thread_call_enter( _disableDebuggerThreadCall ) == TRUE )
{
release();
}
ret = PM_SECS(3);
}
return ret;
}
IOReturn
IOKernelDebugger::powerStateDidChangeTo( IOPMPowerFlags flags,
unsigned long stateNumber,
IOService * policyMaker )
{
IOReturn ret = IOPMAckImplied;
if ( flags & IOPMDeviceUsable )
{
retain();
if ( thread_call_enter( _enableDebuggerThreadCall ) == TRUE )
{
release();
}
ret = PM_SECS(3);
}
return ret;
}
void IOKernelDebugger::pmEnableDebugger( IOKernelDebugger * debugger )
{
assert( debugger );
assert( debugger->_target );
debugger->lockForArbitration();
enableTarget( debugger, debugger->_target, &debugger->_state,
kEventTargetUsable );
if ( debugger->_state & kTargetWasEnabled )
{
registerHandler( debugger->_target,
debugger->_txHandler,
debugger->_rxHandler );
}
debugger->unlockForArbitration();
debugger->_target->acknowledgePowerChange( debugger );
debugger->release();
}
void IOKernelDebugger::pmDisableDebugger( IOKernelDebugger * debugger )
{
assert( debugger );
assert( debugger->_target );
debugger->lockForArbitration();
if ( debugger->_client ) registerHandler( 0 );
disableTarget( debugger, debugger->_target, &debugger->_state,
kEventTargetUsable );
debugger->unlockForArbitration();
debugger->_target->acknowledgePowerChange( debugger );
debugger->release();
}
static void
handleActivationChange( IOKernelDebugger * debugger, void * change )
{
debugger->message( kMessageDebuggerActivationChange, 0, change );
debugger->release();
}
IOReturn IOKernelDebugger::message( UInt32 type, IOService * provider,
void * argument )
{
IOReturn ret = kIOReturnSuccess;
switch ( type )
{
case kMessageControllerWasEnabledForBSD:
case kMessageControllerWasDisabledForBSD:
if ( EARLY_DEBUG_SUPPORT ) break;
retain();
if ( thread_call_enter1( _activationChangeThreadCall,
(void *) type ) == TRUE )
release();
break;
case kMessageDebuggerActivationChange:
lockForArbitration();
if ( (void *) kMessageControllerWasEnabledForBSD == argument )
{
enableTarget( this, _target, &_state, kEventDebuggerActive );
if ( _state & kTargetWasEnabled )
registerHandler( _target, _txHandler, _rxHandler );
}
else
{
if ( _client ) registerHandler( 0 );
disableTarget( this, _target, &_state, kEventDebuggerActive );
}
unlockForArbitration();
break;
default:
ret = super::message( type, provider, argument );
}
return ret;
}