/* IrDAComm.h - Start up IrDA and the IrComm client */

#include <IOKit/IOTimerEventSource.h>
#include <IOKit/IOCommandGate.h>
#include "IrDAComm.h"
#include "IrGlue.h"
#include "IrComm.h"
#include "CTimer.h"
#include "IrDALog.h"

#if (hasTracing > 0 && hasIrDACommTracing > 0)

enum tracecodes
{
    kLogNew = 1,
    kLogFree,
    kLogInit,
    kLogStop,
    kLogStop1,
    kLogStopThread,
    
    kLogTxBufferAvailable,
    kLogWrite,
    kLogReadComplete,
    kLogReturnCredit,
    kLogConnectionStatus,
    kLogTransmitComplete,
    kLogSetSpeedComplete,
    kLogBackEnable,
    
    kLogTimer,
    kLogTimerFinished,
    
    kLogXmitCompleteErr,
    kLogReturnCreditErr,
    
    kLogDoSomething,
    kLogStateChange
};

static
EventTraceCauseDesc gTraceEvents[] = {
    {kLogNew,               "IrDAComm: new, obj="},
    {kLogFree,              "IrDAComm: free, obj="},
    {kLogInit,              "IrDAComm: init, obj="},
    {kLogStop,              "IrDAComm: stop, obj="},
    {kLogStop1,             "IrDAComm: stop waiting for disconnect"},
    {kLogStopThread,        "IrDAComm: stop thread"},
    
    {kLogTxBufferAvailable, "IrDAComm: tx buffer available"},
    {kLogWrite,             "IrDAComm: write, state=,length="},
    {kLogReadComplete,      "IrDAComm: pkt read complete, length="},
    {kLogReturnCredit,      "IrDAComm: return credit, bytecount="},
    {kLogConnectionStatus,  "IrDAComm: new connection status, state=, connected="},
    {kLogTransmitComplete,  "IrDAComm: pkt transmit complete, worked="},
    {kLogSetSpeedComplete,  "IrDAComm: set speed complete"},
    {kLogBackEnable,        "IrDAComm: back enable, write active="},
    
    {kLogTimer,             "IrDAComm: timer routine entry, state="},
    {kLogTimerFinished,     "IrDAComm: timer routine exit"},
    
    {kLogXmitCompleteErr,   "IrDAComm: ERROR. transmit complete while stack is active"},
    {kLogReturnCreditErr,   "IrDAComm: ERROR. return credit while stack is active"},
    
    {kLogDoSomething,       "IrDAComm: run command in gate, cmd code="},
    {kLogStateChange,       "IrDAComm: state change entry, event=, current state="}
};

#define XTRACE(x, y, z) IrDALogAdd ( x, y, z, gTraceEvents, true)
#else
#define XTRACE(x, y, z) ((void)0)
#endif

extern "C" void timeoutRoutine(OSObject *owner, IOTimerEventSource *sender);

enum {          // command codes for DoSomething
    cmdTxBufferAvailable,
    cmdWrite,
    cmdReturnCredit,
    cmdStop,
    cmdStopEvent,
    cmdReadComplete,
    cmdXmitComplete,
    cmdSetSpeedComplete
};

/****************************************************************************************************/
#define super OSObject

    OSDefineMetaClassAndStructors(IrDAComm, OSObject);
    
    
//
// factory create
//
/*static*/
IrDAComm *
IrDAComm::irDAComm(AppleIrDASerial *driver, AppleIrDA *appleirda)
{
    IrDAComm *obj = new IrDAComm;   // create an IrDAComm object
    
    XTRACE(kLogNew, (int)obj >> 16, (short)obj);
    
    if (obj && !obj->init(driver, appleirda)) {
	obj->release();
	obj = nil;
    }
    
    return obj;
}


void IrDAComm::free()
{
    IOWorkLoop *workloop;
    
    XTRACE(kLogFree, (int)this >> 16, (short)this);
    
    this->Stop();       // make sure we're stopped before releasing memory

    if (fDriver) {
	workloop = fDriver->getWorkLoop();
	if (workloop) {
	    if (fGate)
		workloop->removeEventSource(fGate);
	    //if (fTimerSrc)
	    //  workloop->removeEventSource(fTimerSrc);
	}
    }

#define FREE(x) { if (x) { (x)->release(); x = nil; }}

    FREE(fGate);
    FREE(fTimer);
    FREE(fIrComm);          // free the ircomm object before the rest of irda ...
    FREE(fIrDA);
    
#undef FREE

#define THREAD_FREE(x) do { if (x) {               \
			    thread_call_cancel(x); \
			    thread_call_free(x);   \
			    x = NULL; } } while(0)
    
    THREAD_FREE(fStop_thread);
#undef THREAD_FREE
    
    super::free();      // we're done, call super
}
    

/****************************************************************************************************/
//
//      Method:     IrDAComm::Init
//
//      Inputs:     pointer to the usb irda driver
//
//      Outputs:    return code - true (initialized ok), false (it didn't)
//
//      Desc:       Initialize the IrDAComm class
//
/****************************************************************************************************/

bool IrDAComm::init(AppleIrDASerial *driver, AppleIrDA *appleirda)
{
    IOReturn rc;
    IOWorkLoop *workloop;
	
    XTRACE(kLogInit, (int)this >> 16, (short)this);
#if (hasTracing > 0)
    DebugLog("log info at 0x%lx", (UInt32)IrDALogGetInfo());
#endif
    require(driver, Fail);

    fState = kIrDACommStateStart;
    fDriver = driver;
    //fTimerSrc = nil;
    fTimer = nil;
    fQoS = nil;
    fIrDA = nil;
    fIrComm = nil;
    fWriteBusy = false;
    fGate = nil;
    fStartCounter = 0;          // counter for initial connection attempts
    fStop_thread = nil;

    if (!super::init())
	return false;
    
    fQoS = driver->GetIrDAQoS();
    require(fQoS, Fail);
    
    workloop = fDriver->getWorkLoop();
    require(workloop, Fail);
    
    fStop_thread = thread_call_allocate(stop_thread, this);
    require(fStop_thread, Fail);
    
    fIrDA = TIrGlue::tIrGlue(fDriver, appleirda, workloop, fQoS);       // create irda stack
    require(fIrDA, Fail);
    
    fIrComm = IrComm::irComm(fIrDA, this);                                          // create an ircomm object
    require(fIrComm, Fail);
	
    fGate = IOCommandGate::commandGate(this, 0);    // create a new command gate for our access to IrDA
    require(fGate, Fail);
    
    rc = workloop->addEventSource(fGate);           // add it to the usb workloop
    require(rc == kIOReturnSuccess, Fail);

    fTimer = CTimer::cTimer(workloop, this, &IrDAComm::TimerRoutine);
    require(fTimer, Fail);
    
    fTimer->StartTimer(100, 0);                     // 100ms delay after init and then startup

    //fTimerSrc = IOTimerEventSource::timerEventSource ( driver, &::timeoutRoutine);    // create an io timer
    //require(fTimerSrc, Fail);
    //
    //rc = workloop->addEventSource(fTimerSrc);
    //require(rc == kIOReturnSuccess, Fail);
    //      
    //rc = fTimerSrc->setTimeoutMS(100);            // 100 ms delay after init'ing
    //require(rc == kIOReturnSuccess, Fail);

    return true;

Fail:
    return false;
    
} /* end Initialize */


/****************************************************************************************************/
//
//      Method:     IrDAComm::Stop
//
//      Inputs:     
//
//      Outputs:    return code - kIOReturnSuccess
//
//      Desc:       Stops the class (clean up etc.)
//
/****************************************************************************************************/

IOReturn IrDAComm::Stop(void)
{
    int i;
    IOReturn rc = kIOReturnSuccess;
    
    XTRACE(kLogStop, (int)this >> 16, (short)this);
    require(fGate, Fail);
    require(fIrDA, Fail);           // sanity
    require(fDriver, Fail);         // sanity
    
    if (fState != kIrDACommStateStopped) {          // if not already stopped
	boolean_t bt;
	
	if (fDriver->getWorkLoop()->inGate())   {       // if we have the gate, just call it
	    rc = fGate->runAction(&DoSomething, (void *)cmdStopEvent, nil, nil, nil);       
	    check(rc == kIOReturnSuccess);
	}
	else {                                          // we don't have the gate, run stop in another thread and wait for it
	    bt = thread_call_enter(fStop_thread);       // run stop logic in another thread
	    check(bt == false);                         // true here means it was already running and we're confused

	    for (i = 0 ; i < 10; i++) {                 // max wait of a second (should be more than enough)
		XTRACE(kLogStop1, i, fState);
		if (fState == kIrDACommStateStopped &&              // if ircomm is stopped and the irlap link is down
		    fIrDA->IsLAPConnected() == false) break;        // then we're really stopped
		IOSleep(100);                                       // wait 1/10 of a second per state poll
	    }
	}
	check(fState == kIrDACommStateStopped);     // this will fail if in broken-beam, just debugging
	
	rc = fGate->runAction(&DoSomething, (void *)cmdStop, nil, nil, nil);
	check(rc == kIOReturnSuccess);
    }

Fail:
    fState = kIrDACommStateStopped;     // it's really stopped now, regardless of above
    XTRACE(kLogStop, 0xffff, 0xffff);

    return rc;
}

/****************************************************************************************************/
//
//      Method:     IrDAComm::TXBufferAvailable
//
//      Inputs:
//
//      Outputs:    size - number of bytes available for a write
//
//      Desc:       Returns the number of bytes IrDA can currently accept for a write
//
/****************************************************************************************************/

size_t IrDAComm::TXBufferAvailable()
{
    IOReturn rc;
    size_t  result = 0;
    
    XTRACE(kLogTxBufferAvailable, 0, 0);
    
    // we're getting called from outside the usb workloop, so run through our command gate
    
    if (fIrComm && fGate) {
	rc = fGate->runAction(&DoSomething, (void *)cmdTxBufferAvailable, &result);     
	check(rc == kIOReturnSuccess);
    }
	
    XTRACE(kLogTxBufferAvailable, 0xffff, result);
    return result;
	
} /* end TXBufferAvailable */

/****************************************************************************************************/
//
//      Method:     IrDAComm::Write
//
//      Inputs:     Buf - the data to be written, Length - the size of the data
//
//      Outputs:    size - number of bytes written
//
//      Desc:       Queue the data to be written by IrComm
//
/****************************************************************************************************/

size_t IrDAComm::Write(UInt8 *buf, size_t length)
{
    UInt32 result = length;
    IOReturn rc;
    
    XTRACE(kLogWrite, fState, (short)length);

    // this is coming from outside our workloop, send through our gate
     
    if (fState == kIrDACommStateConnected && fIrComm && fGate) {
	rc = fGate->runAction(&DoSomething, (void *)cmdWrite, buf, (void *)length, &result);        
	check(rc == kIOReturnSuccess);
    }

    return result;          // pretend it worked if no connection (i.e. sink to /dev/null)

} /* end Write */

/****************************************************************************************************/
//
//      Method:     IrDAComm::ReadComplete
//
//      Inputs:     Frame - Incoming IrDA frame
//
//      Outputs:    Return code - kIOReturnSuccess or
//
//      Desc:       Process an incoming frame
//
/****************************************************************************************************/

IOReturn IrDAComm::ReadComplete(UInt8 *buf, size_t length)
{
    IOReturn rc;

    XTRACE(kLogReadComplete, length >> 16, (short)length);
    
    if (fGate && fIrComm && fIrDA) {
	rc = fGate->runAction(&DoSomething, (void *)cmdReadComplete, buf, (void *)length, nil);     
    }
    
    return rc;

} /* end ReadComplete */

//
// Sending back flow-control to the peer
//
void
IrDAComm::ReturnCredit(size_t byte_count)       // serial client has consumed count bytes of data
{
    IOReturn rc;
    
    XTRACE(kLogReturnCredit, byte_count >> 16, (short)byte_count);
    
    // this is coming from outside our workloop, send to irda via our command gate
	    
    if (fState == kIrDACommStateConnected && fIrComm && fGate) {
	rc = fGate->runAction(&DoSomething, (void *)cmdReturnCredit, (void *)byte_count);
	check(rc == kIOReturnSuccess);
    }

    return;
}


void
IrDAComm::Transmit_Complete(Boolean worked)
{
    IOReturn rc;

    XTRACE(kLogTransmitComplete, 0, worked);
    
    if (fGate && fIrDA) {
	rc = fGate->runAction(&DoSomething, (void *)cmdXmitComplete, (void *)worked);
    }
    
    XTRACE(kLogTransmitComplete, 0xffff, 0xffff);
}

void
IrDAComm::SetSpeedComplete(Boolean worked)
{
    IOReturn rc;

    XTRACE(kLogSetSpeedComplete, 0, worked);
    
    if (fGate && fIrDA) {
	rc = fGate->runAction(&DoSomething, (void *)cmdSetSpeedComplete, (void *)worked);
    }
    
    XTRACE(kLogSetSpeedComplete, 0xffff, 0xffff);
}



//
// Called by IrCOMM to send data back to the pseudo tty
//
void
IrDAComm::IrCommDataRead(UInt8 *buf, UInt32 length)     // ircomm data to pass back to the tty
{
    if (fDriver)                                // if we're not stopped
	fDriver->Add_RXBytes(buf, length);      // error return?
    else
	DebugLog("IrDAComm data read but no driver");
}

//
// IrComm calls this when our tinytp peer has extended more credit
// to us .. i.e. previously blocked writes may now work.
//
void
IrDAComm::BackEnable(void)
{
    XTRACE(kLogBackEnable, 0, fWriteBusy);
    
    if (fDriver                     // if we're alive and ...
     /* && fWriteBusy */    )               // last time they asked we were busy, then
    // above 'optimization' test stopped the tx queue wheneveer we break up a too-big
    // write into several small writes.  radar 2890966
	    fDriver->SetUpTransmit();       // ask driver code to try to transmit again
}

//
// The state engine for keeping irda in a connect/listen loop.
//

void
IrDAComm::StateChange(int event)
{
    XTRACE(kLogStateChange, event, fState);
    
    require(fIrComm, Fail);
    require(fIrDA, Fail);
    
    switch (fState) {
    
	case kIrDACommStateStart:                   // Starting up and doing initial discovery/connect attempts
		switch (event) {
		
		    case kIrDACommEventTimer:                   // start off the state engine.
			check(fStartCounter == 0);              // sanity
			if (fStartCounter == 0) {
			    fIrDA->Start();                     // start irda stack up
			    fIrComm->TryConnect(1);             // and do the first discovery
			}
			break;
			
		    case kIrDACommEventConnected:               // we're connected!
			fState = kIrDACommStateConnected;
			break;
			
		    case kIrDACommEventDisconnected:            // initial discover/connect failed.  Try again 3 times
			if (fStartCounter++ < 3)
			    fIrComm->TryConnect(1);
			else
			    fState = kIrDACommStateIdle;        // could listen here, but main timer is about to fire soon
			break;
			
		    case kIrDACommEventStop:
			if (fStartCounter == 0)                 // if stopped before we really started
			    fState = kIrDACommStateStopped;     // then we're done w/out doing anything
			else {                                  // else we have a connect pending
			    fState = kIrDACommStateStopping2;   // wait for two callbacks
			    fIrComm->Disconnect();              // abort the connect attempt
			}
			break;
		}
		break;
		    
	case kIrDACommStateIdle:                    // not doing much
		switch (event) {
		
		    case kIrDACommEventTimer:               // time to wake up.  Let's try a discover/connect
			fState = kIrDACommStateConnecting;
			fIrComm->TryConnect(6);
			break;
			
		    case kIrDACommEventConnected:           // connected w/out my doing anything?
			DebugLog("logic error event=%d, state=%d", event, fState);
			break;
			
		    case kIrDACommEventDisconnected:        // disconnect when idle?  seems unlikley
			DebugLog("logic error event=%d, state=%d", event, fState);
			break;
			
		    case kIrDACommEventStop:                // stopped when we're idle
			fState = kIrDACommStateStopped;     // then we're done w/out doing anything
			break;

		}
		break;
		
	case kIrDACommStateConnecting:              // trying to connect
		switch (event) {
		
		    case kIrDACommEventTimer:                   // connect is stuck, likely in broken beam
			DebugLog("connect timing out");
			break;
			
		    case kIrDACommEventConnected:
			fState = kIrDACommStateConnected;       // we've connected!
			break;
			
		    case kIrDACommEventDisconnected:            // connect failed, switch to listen
			fState = kIrDACommStateListening;
			fIrComm->Listen();
			break;
			
		    case kIrDACommEventStop:                    // stop request with connect pending
			fState = kIrDACommStateStopping2;       // wait for two callbacks
			fIrComm->Disconnect();                  // abort the connect attempt
			break;
		}
		break;
		
	case kIrDACommStateListening:               // listening for a peer to discover and connect to us
		switch (event) {
		
		    case kIrDACommEventTimer:                           // Time to stop listening and try a connect again.
			fState = kIrDACommStateStoppingListen;          // Issue a disconnect to abort listen.  We'll get two
			fIrComm->Disconnect();                          // completes, one for listen stopped, and one for disconnect done.
			break;
			
		    case kIrDACommEventConnected:                       // the listen worked, we've got a peer!
			fState = kIrDACommStateConnected;
			break;
			
		    case kIrDACommEventDisconnected:                    // we can get a disconnect if the link bounces, just
			fIrComm->Listen();                              // just keep the listen going
			break;
			
		    case kIrDACommEventStop:                            // stop request when listen pending
			fState = kIrDACommStateStopping2;               // wait for two callbacks
			fIrComm->Disconnect();                          // abort the listen
			break;
		}
		break;
		
	case kIrDACommStateStoppingListen:          // waiting for listen abort to finish it's disconnect
		switch (event) {
		
		    case kIrDACommEventTimer:                           // stuck waiting for listen abort.  likely in broken beam
			DebugLog("stopping listen timer fired, are we stuck?");
			break;
			
		    case kIrDACommEventConnected:                       // connected?  have a disconnect pending.  wait for it.
			DebugLog("listen disconnect race condition");
			break;
			
		    case kIrDACommEventDisconnected:                    // listen aborted.  Now wait for the disconnect complete
			fState = kIrDACommStateDisconnecting;
			break;
			
		    case kIrDACommEventStop:                            // stopped after issuing disconnect to stop listen
			fState = kIrDACommStateStopping2;               // wait for two callbacks
			break;
		}
		break;
		
	case kIrDACommStateDisconnecting:           // waiting for a disconnect request to finish
		switch (event) {
		
		    case kIrDACommEventTimer:                           // stuck doing disconnect, most likely in broken beam
			DebugLog("disconnect timing out?");
			break;
			
		    case kIrDACommEventConnected:                       // connected?  race condition.  Expect a disconnect soon.
			DebugLog("disconnect race condition");
			break;
			
		    case kIrDACommEventDisconnected:                    // listen abort finished, let's try a connect
			fState = kIrDACommStateConnecting;              // switch from listen to connect
			fIrComm->TryConnect(6);                         // a reasonable number of discover slots after quick start
			break;

		    case kIrDACommEventStop:                            // stopped after issuing disconnect
			fState = kIrDACommStateStopping;                // wait for one more callback
			break;
			
		}
		break;
		
	case kIrDACommStateConnected:               // ircomm channel open, data can flow
		switch (event) {
		
		    case kIrDACommEventTimer:                           // nothing to do, we're a happy camper
			break;
			
		    case kIrDACommEventConnected:                       // connected again?  logic error
			DebugLog("logic error event=%d, state=%d", event, fState);
			break;
			
		    case kIrDACommEventDisconnected:                    // lost our connection
			fState = kIrDACommStateIdle;                    // wait for timer before trying to connect again
			break;
			
		    case kIrDACommEventStop:                            // stopped when we're up and running
			fState = kIrDACommStateStopping;                // wait for one callback
			fIrComm->Disconnect();                          // disconnect
			break;
		}
		break;
		
	case kIrDACommStateStopping2:
		switch (event) {
		    case kIrDACommEventTimer:                           // timer should be stopped
			DebugLog("logic error event=%d, state=%d", event, fState);
			break;
			
		    case kIrDACommEventConnected:                       // race condition
			fState = kIrDACommStateStopping;                // should disconnect "real soon" now
			break;
			
		    case kIrDACommEventDisconnected:
			fState = kIrDACommStateStopping;                // normal, listen/connect has aborted
			break;
			
		    case kIrDACommEventStop:
			DebugLog("logic error event=%d, state=%d", event, fState);
			break;
		}
		break;
		
	case kIrDACommStateStopping:
		switch (event) {
		    case kIrDACommEventTimer:
			DebugLog("logic error event=%d, state=%d", event, fState);
			break;
			
		    case kIrDACommEventConnected:
			DebugLog("logic error event=%d, state=%d", event, fState);
			break;
			
		    case kIrDACommEventDisconnected:
			fState = kIrDACommStateStopped;             // stop completed
			if (fIrDA->IsLAPConnected())                // if lap is connected, then
			    fIrDA->DoIdleDisconnect();              // don't wait 1 second, disconnect now
			break;
			
		    case kIrDACommEventStop:
			DebugLog("logic error event=%d, state=%d", event, fState);
			break;
		}
		break;
		
	case kIrDACommStateStopped:         // we're stopped, shouldn't be getting requests
		DebugLog("logic error event=%d, state=%d", event, fState);
		break;
		
    }
Fail:
    return;
}

//
// called by IrComm to let us know of major connection state changes
//
void
IrDAComm::ConnectionStatus(Boolean connected)
{
    static Boolean last_connected = false;      // cut down on the debug log noise
    
    XTRACE(kLogConnectionStatus, fState, connected);
    if (connected != last_connected) {
	DebugLog("connection status %d", connected);
	last_connected = connected;
    }
    check(fState != kIrDACommStateIdle);

    if (connected)  StateChange(kIrDACommEventConnected);
    else            StateChange(kIrDACommEventDisconnected);
}

//
// 5 second timer, keep connection attempts going.
//
/* static */
void
IrDAComm::TimerRoutine(OSObject *owner, IrDATimerEventSource *iotimer)
{ 
    IrDAComm *obj;
    
    XTRACE(kLogTimer, 0, 0);
    
    obj = OSDynamicCast(IrDAComm, owner);
    require(obj, Fail);
    
    XTRACE(kLogTimer, 0x1111, obj->fState);
    
    obj->StateChange(kIrDACommEventTimer);
		    
    if (obj->fIrDA) {                   // now run the irda event queue until it's done
	obj->fIrDA->RunQueue();
    }

    require(obj->fTimer, Fail);
    obj->fTimer->StartTimer(5 * 1000, 0);       // wake up again in 5 seconds

    XTRACE(kLogTimerFinished, 0, 0);
    return;

Fail:
    XTRACE(kLogTimerFinished, 0xdead, 0xbeef);
    return;
}

/****************************************************************************************************/
//
//      Method:     timeoutRoutine
//
//      Inputs:     object handle, timer source
//
//      Outputs:    none
//
//      Desc:       call the real C++ method to handle the timeout
//
/****************************************************************************************************/
/*
extern "C"
void
timeoutRoutine(OSObject *owner, IOTimerEventSource *sender)
{
    AppleIrDASerial *driver = (AppleIrDASerial *)owner;
    IrDAComm *me = driver->GetIrDAComm();
	
    me->TimerRoutine(sender);
}
*/

//
//
//static
IOReturn
IrDAComm::DoSomething(OSObject *owner, void *arg1, void *arg2, void *arg3, void *arg4)
{
    IrDAComm *obj;
    int cmd = (int)arg1;
    
    XTRACE(kLogDoSomething, 0, (short)arg1);        // log the command code
    
    obj = OSDynamicCast(IrDAComm, owner);
    require(obj, Fail);
    
    switch (cmd) {
	case cmdTxBufferAvailable:
			{
			    UInt32  count = 0;
			    UInt32  *result = (UInt32 *)arg2;
			    if (obj->fIrComm)
				count = obj->fIrComm->TxBufferAvailable();
			    obj->fWriteBusy = (count == 0);
			    if (result)
				*result = count;
			}
			break;
	
	case cmdWrite:
			{
			    UInt8   *buf    = (UInt8 *)arg2;
			    UInt32  length  = (UInt32)arg3;
			    UInt32  *result = (UInt32 *)arg4;
			    
			    if (result)
				*result = obj->fIrComm->Write(buf, length);
			}
			break;
			
	case cmdReturnCredit:
			{
			    UInt32  byte_count = (UInt32)arg2;
			    if (obj->fIrComm)
				obj->fIrComm->ReturnCredit(byte_count);
			}
			break;
			
	case cmdStop:
			{
			    if (obj->fTimer) {
				obj->fTimer->StopTimer();
				obj->fTimer->release();
				obj->fTimer = nil;
			    }
			    
			    if (obj->fIrDA) {
				obj->fIrDA->Stop();         // tell irda not to use the driver anymore
			    }
			}
			break;
			
	case cmdStopEvent:
			check(obj->fTimer);
			if (obj->fTimer) {
			    obj->fTimer->StopTimer();                   // stop the timer
			    obj->StateChange(kIrDACommEventStop);       // get the state engine to stop
			}
			if (obj->fIrDA) {                   // now run the irda event queue until it's done
			    obj->fIrDA->RunQueue();
			}
			break;
	
	case cmdReadComplete:
			{
			    UInt8   *buf    = (UInt8 *)arg2;
			    UInt32  length  = (UInt32)arg3;
			    if (obj->fIrDA) {
				obj->fIrDA->ReadComplete(buf, length);
				obj->fIrDA->RunQueue();
			    }
			}
			break;
			
	case cmdXmitComplete:
			{
			    bool    worked = (bool)arg2;
			    if (obj->fIrDA) {
				obj->fIrDA->TransmitComplete(worked);
				XTRACE(kLogTransmitComplete, 0x1111, 0x1111);
				obj->fIrDA->RunQueue();
				XTRACE(kLogTransmitComplete, 0x2222, 0x2222);
			    }
			}
			break;
			
	case cmdSetSpeedComplete:
			{
			    bool    worked = (bool)arg2;
			    if (obj->fIrDA) {
				obj->fIrDA->SetSpeedComplete(worked);
				XTRACE(kLogSetSpeedComplete, 0x1111, 0x1111);
				obj->fIrDA->RunQueue();
				XTRACE(kLogSetSpeedComplete, 0x2222, 0x2222);
			    }
			}
			break;
			
	default:
			check(0);
			break;      
    }
			
    return kIOReturnSuccess;

Fail:
    return kIOReturnBadArgument;
}

//
// return irda status to user-client
//
void
IrDAComm::GetIrDAStatus(IrDAStatus *status)
{
    if (fIrDA && status)
	fIrDA->GetIrDAStatus(status);
}

//
// return true if starting up (initial connection attempt)
//
bool
IrDAComm::Starting()
{
    return fState == kIrDACommStateStart;
}


void IrDAComm::stop_thread(thread_call_param_t param0, thread_call_param_t param1)
{
    IrDAComm *obj;
    IOReturn rc;
    
    XTRACE(kLogStopThread, 0, 0);
    
    require(param0, Fail);
    obj = OSDynamicCast(IrDAComm, (OSObject *)param0);
    require(obj, Fail);
    
    rc = obj->fGate->runAction(&DoSomething, (void *)cmdStopEvent, nil, nil, nil);      
    check(rc == kIOReturnSuccess);

Fail:
    return;
}
