/*
 * Copyright (c) 1998-2000 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@
 */
/*
 * Copyright (c) 1999 Apple Computer, Inc.  All rights reserved. 
 *
 * IOKernelDebugger.cpp
 *
 * HISTORY
 */

#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 <libkern/OSAtomic.h>

//---------------------------------------------------------------------------
// IOKDP

#define kIOKDPEnableKDP            "IOEnableKDP"
#define kIOKDPDriverMatchPPC       "IODriverMatchPPC"
#define kIOKDPDriverNubMatchPPC    "IODriverNubMatchPPC"
#define kIOKDPDriverMatchI386      "IODriverMatchI386"
#define kIOKDPDriverNubMatchI386   "IODriverNubMatchI386"

class IOKDP : public IOService
{
    OSDeclareDefaultStructors( IOKDP )

public:
    virtual bool start( IOService * provider );

    virtual void stop( IOService * provider );

    virtual bool matchProvider( IOService * provider );

    virtual bool matchServiceWithDictionary( IOService *    service,
                                             OSDictionary * match );

    virtual IOReturn message( UInt32       type,
                              IOService *  provider,
                              void *       argument = 0 );
};

//---------------------------------------------------------------------------
// IOKDP defined globals.

static IOLock * gIOKDPLock = 0;
static IOKDP *  gIOKDP     = 0;

class IOKDPGlobals
{
public:
    IOKDPGlobals();
    ~IOKDPGlobals();
    
    inline bool isValid() const;
};

static IOKDPGlobals gIOKDPGlobals;

IOKDPGlobals::IOKDPGlobals()
{
    gIOKDPLock = IOLockAlloc();
}

IOKDPGlobals::~IOKDPGlobals()
{
    if ( gIOKDPLock )
    {
        IOLockFree( gIOKDPLock );
        gIOKDPLock = 0;
    }
}

bool IOKDPGlobals::isValid() const
{
    return ( gIOKDPLock );
}

#define super IOService
OSDefineMetaClassAndStructors( IOKDP, IOService )

//---------------------------------------------------------------------------
// Match the provider with the matching dictionary in our property table.

bool IOKDP::matchProvider(IOService * provider)
{
    IOService * driver    = 0;
    IOService * driverNub = 0;
    OSBoolean * aBool;

    if ( provider )  driver = provider->getProvider();
    if ( driver ) driverNub = driver->getProvider();

    if ( (driver == 0) || (driverNub == 0) )
        return false;

    if ( ( aBool = OSDynamicCast(OSBoolean, getProperty(kIOKDPEnableKDP)) ) &&
         ( aBool->isTrue() == false ) )
        return false;

#if defined (__ppc__)

    if ( matchServiceWithDictionary( driver, (OSDictionary *)
                                     getProperty(kIOKDPDriverMatchPPC)) )
    {
        // IOLog("IOKDP: %s\n", kIOKDPDriverMatch);
        return true;
    }

    if ( matchServiceWithDictionary( driverNub, (OSDictionary *) 
                                     getProperty(kIOKDPDriverNubMatchPPC)) )
    {
        // IOLog("IOKDP: %s\n", kIOKDPDriverNubMatch);
        return true;
    }

#elif defined (__i386__)

	return true;

#else
#error unknown architecture
#endif

    return false;
}

//---------------------------------------------------------------------------
// Match an IOService with a matching dictionary.

bool IOKDP::matchServiceWithDictionary(IOService *    service,
                                       OSDictionary * match)
{
    OSCollectionIterator * matchIter;
    OSCollectionIterator * arrayIter = 0;
    OSCollection *         array;
    OSObject *             objM;
    OSObject *             objP;
    OSSymbol *             sym;
    bool                   isMatch = false;

    if ( ( OSDynamicCast(OSDictionary, match) == 0 ) ||
         ( match->getCount() == 0 )                  ||
         ( (matchIter = OSCollectionIterator::withCollection(match)) == 0 ) )
        return false;

    while ( ( sym = OSDynamicCast(OSSymbol, matchIter->getNextObject()) ) )
    {
        objM = match->getObject(sym);
        objP = service->getProperty(sym);

        isMatch = false;

        if ( arrayIter )
        {
            arrayIter->release();
            arrayIter = 0;
        }

        if ( (array = OSDynamicCast( OSCollection, objM )) )
        {
            arrayIter = OSCollectionIterator::withCollection( array );
            if ( arrayIter == 0 ) break;
        }

        do {
            if ( arrayIter && ((objM = arrayIter->getNextObject()) == 0) )
                break;

            if ( objM && objP && objM->isEqualTo(objP) )
            {
                isMatch = true;
                break;
            }
        }
        while ( arrayIter );

        if ( isMatch == false ) break;
    }

    if ( arrayIter ) arrayIter->release();
    matchIter->release();

    return isMatch;
}

//---------------------------------------------------------------------------
// start/stop/message.

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 ( matchProvider(provider) == false )
            break;

        if ( provider->open(this) == false )
            break;

        publishResource("kdp");

        gIOKDP = this;
        ret    = true;
    }
    while ( false );

    IOLockUnlock( gIOKDPLock );

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


//---------------------------------------------------------------------------
// IOKernelDebugger

extern "C" {
//
// Defined in osfmk/kdp/kdp_en_debugger.h, but the header file is not
// exported, thus the definition is replicated here.
//
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 );
}

#undef  super
#define super IOService
OSDefineMetaClassAndStructors( IOKernelDebugger, IOService )
OSMetaClassDefineReservedUnused( IOKernelDebugger,  0);
OSMetaClassDefineReservedUnused( IOKernelDebugger,  1);
OSMetaClassDefineReservedUnused( IOKernelDebugger,  2);
OSMetaClassDefineReservedUnused( IOKernelDebugger,  3);

// IOKernelDebugger global variables.
//
IOService *          gIODebuggerDevice    = 0;
IODebuggerTxHandler  gIODebuggerTxHandler = 0;
IODebuggerRxHandler  gIODebuggerRxHandler = 0;
UInt32               gIODebuggerTxBytes   = 0;
UInt32               gIODebuggerRxBytes   = 0;
SInt32               gIODebuggerSemaphore = 0;
UInt32               gIODebuggerFlag      = 0;

// Global debugger flags.
// 
enum {
    kIODebuggerFlagRegistered       = 0x01,
    kIODebuggerFlagWarnNullHandler  = 0x02
};

//---------------------------------------------------------------------------
// The KDP receive dispatch function. Dispatches KDP receive requests to the
// registered receive handler. This function is registered with KDP via 
// kdp_register_send_receive().

void IOKernelDebugger::kdpReceiveDispatcher( void *   buffer,
                                             UInt32 * length, 
                                             UInt32   timeout )
{
    *length = 0;    // return a zero length field by default.

    if ( gIODebuggerSemaphore ) return;  // FIXME - Driver is busy!

    (*gIODebuggerRxHandler)( gIODebuggerDevice, buffer, length, timeout );

    gIODebuggerRxBytes += *length;
}

//---------------------------------------------------------------------------
// The KDP transmit dispatch function. Dispatches KDP receive requests to the
// registered transmit handler. This function is registered with KDP via 
// kdp_register_send_receive().

void IOKernelDebugger::kdpTransmitDispatcher( void * buffer, UInt32 length )
{
    if ( gIODebuggerSemaphore ) return;  // FIXME - Driver is busy!

    (*gIODebuggerTxHandler)( gIODebuggerDevice, buffer, length );

    gIODebuggerTxBytes += length;
}

//---------------------------------------------------------------------------
// Null debugger handlers.

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

//---------------------------------------------------------------------------
// Take the debugger lock conditionally.

IODebuggerLockState IOKernelDebugger::lock( IOService * object )
{
    if ( gIODebuggerDevice == object )
    {
        OSIncrementAtomic( &gIODebuggerSemaphore );
        return kIODebuggerLockTaken;
    }
    return (IODebuggerLockState) 0;
}

//---------------------------------------------------------------------------
// Release the debugger lock if the kIODebuggerLockTaken flag is set.

void IOKernelDebugger::unlock( IODebuggerLockState state )
{
    if ( state & kIODebuggerLockTaken )
        OSDecrementAtomic( &gIODebuggerSemaphore );
}

//---------------------------------------------------------------------------
// Initialize an IOKernelDebugger instance.

bool IOKernelDebugger::init( IOService *          target,
                             IODebuggerTxHandler  txHandler,
                             IODebuggerRxHandler  rxHandler )
{
    if ( ( super::init() == false )                ||
         ( OSDynamicCast(IOService, target) == 0 ) ||
         ( txHandler == 0 )                        ||
         ( rxHandler == 0 ) )
    {
        return false;
    }

    // Cache the target and handlers provided.

    _target     = target;
    _txHandler  = txHandler;
    _rxHandler  = rxHandler;

    return true;
}

//---------------------------------------------------------------------------
// Factory method which performs allocation and initialization of an 
// IOKernelDebugger instance.

IOKernelDebugger * IOKernelDebugger::debugger( IOService *          target,
                                               IODebuggerTxHandler  txHandler,
                                               IODebuggerRxHandler  rxHandler )
{
    IOKernelDebugger * debugger = new IOKernelDebugger;

    if (debugger && (debugger->init( target, txHandler, rxHandler ) == false))
    {
        debugger->release();
        debugger = 0;
    }

    return debugger;
}

//---------------------------------------------------------------------------
// Register the debugger handlers.

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 ( 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) )
    {
        // Register dispatch function, these in turn will call the
        // handlers when the debugger is active.
        // 
        // Note: The following call may trigger an immediate break
        //       to the debugger.

        kdp_register_send_receive( (kdp_send_t) kdpTransmitDispatcher,
                                   (kdp_receive_t) kdpReceiveDispatcher );

        // Limit ourself to a single real KDP registration.

        gIODebuggerFlag |= kIODebuggerFlagRegistered;
    }
}

//---------------------------------------------------------------------------
// Called by open() with the arbitration lock held.

bool IOKernelDebugger::handleOpen( IOService *    forClient,
                                   IOOptionBits   options,
                                   void *         arg )
{
    IONetworkController * ctr = OSDynamicCast(IONetworkController, _target);
    bool                  ret = false;

    do {
        // Only a single client at a time.

        if ( _client ) break;

        // Register the target to prime the lock()/unlock() functionality
        // before opening the target.

        registerHandler( _target );

        // While the target is opened/enabled, it must block any thread
        // which may acquire the debugger lock in its execution path.

        if ( _target->open( this ) == false )
            break;

        // Register interest in receiving notifications about controller
        // power state changes.
        //
        // We are making an assumption that the controller is 'usable' and
        // the next notification will inform this object that the controller
        // has become unusable, there is no support for cases when the
        // controller is already in an 'unusable' state.

        _pmDisabled = false;

        if ( ctr )
        {
            // Register to receive PM notifications for controller power
            // state changes.

            ctr->registerInterestedDriver( this );
        
            if ( ctr->doEnable( this ) != kIOReturnSuccess )
            {
                ctr->deRegisterInterestedDriver( this );
                break;
            }
        }

        // After the target has been opened, complete the registration.

        IOLog("%s: Debugger attached\n", getName());
        registerHandler( _target, _txHandler, _rxHandler );

        // Remember the client.

        _client = forClient;

        ret = true;
    }
    while (0);

    if ( ret == false )
    {
        registerHandler( 0 );
        _target->close( this );
    }

    return ret;
}

//---------------------------------------------------------------------------
// Called by IOService::close() with the arbitration lock held.

void IOKernelDebugger::handleClose( IOService *   forClient,
                                    IOOptionBits  options )
{
    IONetworkController * ctr = OSDynamicCast(IONetworkController, _target);

    if ( _client && ( _client == forClient ) )
    {
        // There is no KDP un-registration. The best we can do is to
        // register dummy handlers.

        registerHandler( 0 );

        if ( ctr )
        {
            // Disable controller if it is not already disabled.

            if ( _pmDisabled == false )
            {
                ctr->doDisable( this );
            }

            // Before closing the controller, remove interest in receiving
            // notifications about controller power state changes.

            ctr->deRegisterInterestedDriver( this );
        }

        _client = 0;

        _target->close( this );
    }
}

//---------------------------------------------------------------------------
// Called by IOService::isOpen() with the arbitration lock held.

bool IOKernelDebugger::handleIsOpen( const IOService * forClient ) const
{
    if ( forClient == 0 )
        return ( forClient != _client );
    else
        return ( forClient == _client );
}

//---------------------------------------------------------------------------
// Free the IOKernelDebugger object.

void IOKernelDebugger::free()
{
    // IOLog("IOKernelDebugger::%s %p\n", __FUNCTION__, this);
    super::free();
}

#define PM_SECS(x)    ((x) * 1000 * 1000)

//---------------------------------------------------------------------------
// Handle controller's power state change notitifications.

IOReturn
IOKernelDebugger::powerStateWillChangeTo( IOPMPowerFlags  flags,
                                          unsigned long   stateNumber,
                                          IOService *     policyMaker )
{
    IOReturn ret = IOPMAckImplied;

    if ( ( flags & IOPMDeviceUsable ) == 0 )
    {
        // Controller is about to transition to an un-usable state.
        // The debugger nub should be disabled.

        this->retain();

        thread_call_func( (thread_call_func_t) pmDisableDebugger,
                          this,    /* parameter */
                          FALSE ); /* disable unique call filter */

        ret = PM_SECS(3);  /* Must ACK within 3 seconds */
    }

    return ret;
}

IOReturn
IOKernelDebugger::powerStateDidChangeTo( IOPMPowerFlags  flags,
                                         unsigned long   stateNumber,
                                         IOService *     policyMaker )
{
    IOReturn ret = IOPMAckImplied;

    if ( flags & IOPMDeviceUsable )
    {
        // Controller has transitioned to an usable state.
        // The debugger nub should be enabled if necessary.

        this->retain();

        thread_call_func( (thread_call_func_t) pmEnableDebugger,
                          this,    /* parameter */
                          FALSE ); /* disable unique call filter */

        ret = PM_SECS(3);  /* Must ACK within 3 seconds */
    }

    return ret;
}

//---------------------------------------------------------------------------
// Static member function: Enable the debugger nub after the controller
// transitions into an usable state.

void IOKernelDebugger::pmEnableDebugger( IOKernelDebugger * debugger )
{
    IONetworkController * ctr;
    assert( debugger );

    ctr = OSDynamicCast( IONetworkController, debugger->_target );

    debugger->lockForArbitration();

    if ( debugger->_client && ( debugger->_pmDisabled == true ) )
    {
        if ( ctr && ( ctr->doEnable( debugger ) != kIOReturnSuccess ) )
        {
            // This is bad, unable to re-enable the controller after sleep.
            IOLog("IOKernelDebugger: Unable to re-enable controller\n");
        }
        else
        {
            registerHandler( debugger->_target, debugger->_txHandler, 
                                                debugger->_rxHandler );

            debugger->_pmDisabled = false;
        }
    }

    debugger->unlockForArbitration();

    // Ack the power state change.
    debugger->_target->acknowledgePowerChange( debugger );

    debugger->release();
}

//---------------------------------------------------------------------------
// Static member function: Disable the debugger nub before the controller
// transitions into an unusable state.

void IOKernelDebugger::pmDisableDebugger( IOKernelDebugger * debugger )
{
    IONetworkController * ctr;
    assert( debugger );

    ctr = OSDynamicCast( IONetworkController, debugger->_target );

    debugger->lockForArbitration();

    if ( debugger->_client && ( debugger->_pmDisabled == false ) )
    {
        // Keep an open on the controller, but inhibit access to the
        // controller's debugger handlers, and disable controller's
        // hardware support for the debugger.

        registerHandler( 0 );
        if ( ctr ) ctr->doDisable( debugger );

        debugger->_pmDisabled = true;
    }

    debugger->unlockForArbitration();

    // Ack the power state change.
    debugger->_target->acknowledgePowerChange( debugger );

    debugger->release();
}
